modification 2026
This commit is contained in:
parent
865410ae93
commit
58154af680
@ -1,5 +1,3 @@
|
||||
// Remplacez complètement votre fichier CommandeDetails par celui-ci :
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:youmazgestion/Models/client.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(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
@ -66,7 +65,9 @@ class CommandeDetails extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
} 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 {
|
||||
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: [
|
||||
Icon(
|
||||
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),
|
||||
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(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: hasRemises ? Colors.orange.shade800 : Colors.black87,
|
||||
color:
|
||||
hasRemises ? Colors.orange.shade800 : Colors.black87,
|
||||
),
|
||||
),
|
||||
if (hasRemises) ...[
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@ -255,6 +264,17 @@ class CommandeDetails extends StatelessWidget {
|
||||
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) ...[
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
|
||||
@ -53,7 +53,8 @@ class Client {
|
||||
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();
|
||||
}
|
||||
|
||||
@ -73,11 +74,7 @@ class Client {
|
||||
String get nomComplet => '$prenom $nom';
|
||||
}
|
||||
|
||||
enum StatutCommande {
|
||||
enAttente,
|
||||
confirmee,
|
||||
annulee
|
||||
}
|
||||
enum StatutCommande { enAttente, confirmee, annulee }
|
||||
|
||||
class Commande {
|
||||
final int? id;
|
||||
@ -173,10 +170,7 @@ class Commande {
|
||||
}
|
||||
|
||||
// REMPLACEZ COMPLÈTEMENT votre classe DetailCommande dans Models/client.dart par celle-ci :
|
||||
enum RemiseType {
|
||||
pourcentage,
|
||||
montant
|
||||
}
|
||||
enum RemiseType { pourcentage, montant }
|
||||
|
||||
class DetailCommande {
|
||||
final int? id;
|
||||
@ -193,6 +187,7 @@ class DetailCommande {
|
||||
final String? produitNom;
|
||||
final String? produitImage;
|
||||
final String? produitReference;
|
||||
final String? produitImei; // NOUVEAU : IMEI du produit, si applicable
|
||||
|
||||
DetailCommande({
|
||||
this.id,
|
||||
@ -209,6 +204,7 @@ class DetailCommande {
|
||||
this.produitNom,
|
||||
this.produitImage,
|
||||
this.produitReference,
|
||||
this.produitImei,
|
||||
});
|
||||
|
||||
// Constructeur pour créer un détail sans remise
|
||||
@ -222,6 +218,7 @@ class DetailCommande {
|
||||
String? produitNom,
|
||||
String? produitImage,
|
||||
String? produitReference,
|
||||
String? produitImei,
|
||||
}) {
|
||||
final sousTotal = quantite * prixUnitaire;
|
||||
final prixFinal = estCadeau ? 0.0 : sousTotal;
|
||||
@ -238,6 +235,7 @@ class DetailCommande {
|
||||
produitNom: produitNom,
|
||||
produitImage: produitImage,
|
||||
produitReference: produitReference,
|
||||
produitImei: produitImei,
|
||||
);
|
||||
}
|
||||
|
||||
@ -251,6 +249,7 @@ class DetailCommande {
|
||||
String? produitNom,
|
||||
String? produitImage,
|
||||
String? produitReference,
|
||||
String? produitImei,
|
||||
}) {
|
||||
return DetailCommande(
|
||||
id: id,
|
||||
@ -264,6 +263,7 @@ class DetailCommande {
|
||||
produitNom: produitNom,
|
||||
produitImage: produitImage,
|
||||
produitReference: produitReference,
|
||||
produitImei: produitImei,
|
||||
);
|
||||
}
|
||||
|
||||
@ -301,6 +301,7 @@ class DetailCommande {
|
||||
produitNom: produitNom,
|
||||
produitImage: produitImage,
|
||||
produitReference: produitReference,
|
||||
produitImei: produitImei,
|
||||
);
|
||||
}
|
||||
|
||||
@ -321,6 +322,7 @@ class DetailCommande {
|
||||
produitNom: produitNom,
|
||||
produitImage: produitImage,
|
||||
produitReference: produitReference,
|
||||
produitImei: produitImei,
|
||||
);
|
||||
}
|
||||
|
||||
@ -341,6 +343,7 @@ class DetailCommande {
|
||||
produitNom: produitNom,
|
||||
produitImage: produitImage,
|
||||
produitReference: produitReference,
|
||||
produitImei: produitImei,
|
||||
);
|
||||
}
|
||||
|
||||
@ -361,11 +364,13 @@ class DetailCommande {
|
||||
produitNom: produitNom,
|
||||
produitImage: produitImage,
|
||||
produitReference: produitReference,
|
||||
produitImei: produitImei,
|
||||
);
|
||||
}
|
||||
|
||||
// Getters utiles
|
||||
bool get aRemise => remiseType != null && montantRemise > 0 && !estCadeau;
|
||||
bool get aimei => produitImei != null;
|
||||
|
||||
double get pourcentageRemise {
|
||||
if (!aRemise) return 0.0;
|
||||
@ -432,6 +437,7 @@ class DetailCommande {
|
||||
produitNom: map['produitNom'] as String?,
|
||||
produitImage: map['produitImage'] as String?,
|
||||
produitReference: map['produitReference'] as String?,
|
||||
produitImei: map['produitImei'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -47,10 +47,9 @@ class AppDatabase {
|
||||
await insertDefaultPointsDeVente();
|
||||
}
|
||||
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
String _formatDate(DateTime date) {
|
||||
return DateFormat('yyyy-MM-dd HH:mm:ss').format(date);
|
||||
}
|
||||
}
|
||||
|
||||
Future<MySqlConnection> _initDB() async {
|
||||
try {
|
||||
@ -608,7 +607,8 @@ String _formatDate(DateTime date) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Future<double> getValeurTotaleStock() async {
|
||||
|
||||
Future<double> getValeurTotaleStock() async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
@ -622,8 +622,7 @@ Future<double> getValeurTotaleStock() async {
|
||||
print('Erreur lors du calcul de la valeur totale du stock : $e');
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --- STATISTIQUES ---
|
||||
|
||||
@ -914,7 +913,8 @@ Future<double> getValeurTotaleStock() async {
|
||||
dc.*,
|
||||
p.name as produitNom,
|
||||
p.image as produitImage,
|
||||
p.reference as produitReference
|
||||
p.reference as produitReference,
|
||||
p.imei as produitImei
|
||||
FROM details_commandes dc
|
||||
LEFT JOIN products p ON dc.produitId = p.id
|
||||
WHERE dc.commandeId = ?
|
||||
@ -923,13 +923,14 @@ Future<double> getValeurTotaleStock() async {
|
||||
|
||||
return result.map((row) => DetailCommande.fromMap(row.fields)).toList();
|
||||
}
|
||||
Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
|
||||
Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
int pointVenteId, {
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
bool aujourdHuiSeulement = false,
|
||||
int limit = 50,
|
||||
}) async {
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
@ -939,7 +940,8 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
if (aujourdHuiSeulement) {
|
||||
final today = DateTime.now();
|
||||
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 <= ?';
|
||||
whereArgs.addAll([
|
||||
@ -947,7 +949,8 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
_formatDate(endOfDay),
|
||||
]);
|
||||
} 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 <= ?';
|
||||
whereArgs.addAll([
|
||||
_formatDate(dateDebut),
|
||||
@ -957,7 +960,8 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
whereClause += ' AND c.dateCommande >= ?';
|
||||
whereArgs.add(_formatDate(dateDebut));
|
||||
} 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 <= ?';
|
||||
whereArgs.add(_formatDate(adjustedEndDate));
|
||||
}
|
||||
@ -991,7 +995,7 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
print('Erreur récupération commandes: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- RECHERCHE PRODUITS ---
|
||||
|
||||
@ -1026,9 +1030,10 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
|
||||
Future<List<Map<String, dynamic>>> getPointsDeVentes() async {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Product>> getProductsByCategory(String category) async {
|
||||
final db = await database;
|
||||
@ -1241,7 +1246,6 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
return result.affectedRows!;
|
||||
}
|
||||
|
||||
|
||||
// Future<int> deletePointDeVente(int id) async {
|
||||
// final db = await database;
|
||||
// final result = await db.query('DELETE FROM points_de_vente WHERE id = ?', [id]);
|
||||
@ -1434,8 +1438,9 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
|
||||
final result =
|
||||
await db.query('SELECT * FROM points_de_vente WHERE id = ?', [id]);
|
||||
return result.isNotEmpty ? result.first : null;
|
||||
}
|
||||
List<String> parseHeaderInfo(dynamic blobData) {
|
||||
}
|
||||
|
||||
List<String> parseHeaderInfo(dynamic blobData) {
|
||||
if (blobData == null) return [];
|
||||
|
||||
try {
|
||||
@ -1472,10 +1477,7 @@ List<String> parseHeaderInfo(dynamic blobData) {
|
||||
print('❌ Erreur lors du parsing des données d\'en-tête: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Future<int?> getOrCreatePointDeVenteByNom(String nom) async {
|
||||
final db = await database;
|
||||
@ -1976,7 +1978,7 @@ List<String> parseHeaderInfo(dynamic blobData) {
|
||||
}
|
||||
|
||||
// 3. Méthodes pour les commandes
|
||||
Future<int> updateStatutCommande(
|
||||
Future<int> updateStatutCommande(
|
||||
int commandeId, StatutCommande statut) async {
|
||||
final db = await database;
|
||||
|
||||
@ -2016,8 +2018,7 @@ Future<int> updateStatutCommande(
|
||||
print("Erreur lors de la mise à jour du statut de la commande: $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Future<List<Commande>> getCommandesByClient(int clientId) async {
|
||||
final db = await database;
|
||||
@ -2557,12 +2558,13 @@ Future<int> updateStatutCommande(
|
||||
|
||||
return erreurs;
|
||||
}
|
||||
|
||||
// --- MÉTHODES POUR LES VENTES PAR POINT DE VENTE ---
|
||||
Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
|
||||
Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
bool? aujourdHuiSeulement = false,
|
||||
}) async {
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
@ -2573,7 +2575,8 @@ Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
|
||||
if (aujourdHuiSeulement == true) {
|
||||
final today = DateTime.now();
|
||||
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 <= ?';
|
||||
whereArgs.addAll([
|
||||
@ -2615,15 +2618,15 @@ Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
|
||||
print("Erreur getVentesParPointDeVente: $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
int pointDeVenteId, {
|
||||
int limit = 5,
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
bool? aujourdHuiSeulement = false,
|
||||
}) async {
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
@ -2633,7 +2636,8 @@ Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
if (aujourdHuiSeulement == true) {
|
||||
final today = DateTime.now();
|
||||
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 <= ?';
|
||||
whereArgs.addAll([
|
||||
@ -2641,7 +2645,8 @@ Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
_formatDate(endOfDay),
|
||||
]);
|
||||
} 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 <= ?';
|
||||
whereArgs.addAll([
|
||||
_formatDate(dateDebut),
|
||||
@ -2672,7 +2677,7 @@ Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
print("Erreur getTopProduitsParPointDeVente: $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getVentesParPointDeVenteParMois(
|
||||
int pointDeVenteId) async {
|
||||
@ -3374,9 +3379,8 @@ Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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,
|
||||
String? statut,
|
||||
int? pointDeVenteId,
|
||||
@ -3384,7 +3388,7 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
DateTime? dateFin,
|
||||
bool aujourdHuiSeulement = false,
|
||||
int limit = 50,
|
||||
}) async {
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
@ -3398,12 +3402,14 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
}
|
||||
|
||||
if (adminId != null) {
|
||||
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.admin_id = ?';
|
||||
whereClause +=
|
||||
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.admin_id = ?';
|
||||
params.add(adminId);
|
||||
}
|
||||
|
||||
if (statut != null) {
|
||||
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.statut = ?';
|
||||
whereClause +=
|
||||
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.statut = ?';
|
||||
params.add(statut);
|
||||
}
|
||||
|
||||
@ -3411,27 +3417,34 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
if (aujourdHuiSeulement) {
|
||||
final today = DateTime.now();
|
||||
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') +
|
||||
' sp.date_sortie >= ? AND sp.date_sortie <= ?';
|
||||
params.add(startOfDay.toIso8601String());
|
||||
params.add(endOfDay.toIso8601String());
|
||||
} else if (dateDebut != null && dateFin != null) {
|
||||
final startOfDay = DateTime(dateDebut.year, dateDebut.month, dateDebut.day);
|
||||
final endOfDay = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
|
||||
final startOfDay =
|
||||
DateTime(dateDebut.year, dateDebut.month, dateDebut.day);
|
||||
final endOfDay =
|
||||
DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
|
||||
|
||||
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') +
|
||||
' sp.date_sortie >= ? AND sp.date_sortie <= ?';
|
||||
params.add(startOfDay.toIso8601String());
|
||||
params.add(endOfDay.toIso8601String());
|
||||
} else if (dateDebut != null) {
|
||||
final startOfDay = DateTime(dateDebut.year, dateDebut.month, dateDebut.day);
|
||||
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie >= ?';
|
||||
final startOfDay =
|
||||
DateTime(dateDebut.year, dateDebut.month, dateDebut.day);
|
||||
whereClause +=
|
||||
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie >= ?';
|
||||
params.add(startOfDay.toIso8601String());
|
||||
} else if (dateFin != null) {
|
||||
final endOfDay = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
|
||||
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie <= ?';
|
||||
final endOfDay =
|
||||
DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
|
||||
whereClause +=
|
||||
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie <= ?';
|
||||
params.add(endOfDay.toIso8601String());
|
||||
}
|
||||
|
||||
@ -3459,8 +3472,9 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
||||
print('Erreur récupération historique sorties: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Future<void> updatePointDeVentes(
|
||||
}
|
||||
|
||||
Future<void> updatePointDeVentes(
|
||||
int id,
|
||||
String nom,
|
||||
String code, {
|
||||
@ -3468,7 +3482,7 @@ Future<void> updatePointDeVentes(
|
||||
String? livraison,
|
||||
String? facture,
|
||||
Uint8List? imagePath,
|
||||
}) async {
|
||||
}) async {
|
||||
final db = await database;
|
||||
|
||||
try {
|
||||
@ -3496,11 +3510,7 @@ Future<void> updatePointDeVentes(
|
||||
print('Stacktrace : $stacktrace');
|
||||
rethrow; // si tu veux faire remonter l’erreur plus haut
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getStatistiquesSortiesPersonnelles() async {
|
||||
final db = await database;
|
||||
@ -3559,7 +3569,6 @@ Future<void> updatePointDeVentes(
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _formatDate {
|
||||
}
|
||||
|
||||
class _formatDate {}
|
||||
|
||||
@ -189,7 +189,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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),
|
||||
TextField(
|
||||
controller: amountController,
|
||||
@ -279,7 +280,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
|
||||
///
|
||||
// Dans GestionCommandesPage - Remplacez la méthode _generateBonLivraison complète
|
||||
|
||||
Future<void> _generateBonLivraison(Commande commande) async {
|
||||
Future<void> _generateBonLivraison(Commande commande) async {
|
||||
final details = await _database.getDetailsCommande(commande.id!);
|
||||
final client = await _database.getClientById(commande.clientId);
|
||||
final pointDeVenteId = commande.pointDeVenteId;
|
||||
@ -288,12 +289,11 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
||||
// ✅ MODIFICATION: Récupération complète des données du point de vente
|
||||
if (pointDeVenteId != null) {
|
||||
pointDeVenteComplet = await _database.getPointDeVenteById(pointDeVenteId);
|
||||
} else {
|
||||
} else {
|
||||
print("ce point de vente n'existe pas");
|
||||
}
|
||||
}
|
||||
final pointDeVente = pointDeVenteComplet;
|
||||
|
||||
|
||||
// Récupérer les informations des vendeurs
|
||||
final commandeur = commande.commandeurId != null
|
||||
? await _database.getUserById(commande.commandeurId!)
|
||||
@ -318,7 +318,6 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
||||
print('===============================');
|
||||
}
|
||||
|
||||
|
||||
// Infos par défaut si aucune info personnalisée
|
||||
final infosLivraisonDefaut = [
|
||||
'REMAX Andravoangy',
|
||||
@ -394,8 +393,10 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
||||
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'));
|
||||
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');
|
||||
}
|
||||
@ -404,11 +405,19 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
||||
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 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);
|
||||
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);
|
||||
Future<pw.Widget> buildLogoWidget() async {
|
||||
final logoRaw = pointDeVenteComplet?['logo'];
|
||||
|
||||
@ -424,7 +433,8 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
||||
dynamic blobDynamic = logoRaw;
|
||||
bytes = blobDynamic.toBytes();
|
||||
} else {
|
||||
throw Exception("Format de logo non supporté: ${logoRaw.runtimeType}");
|
||||
throw Exception(
|
||||
"Format de logo non supporté: ${logoRaw.runtimeType}");
|
||||
}
|
||||
|
||||
final imageLogo = pw.MemoryImage(bytes);
|
||||
@ -434,14 +444,14 @@ Future<void> _generateBonLivraison(Commande commande) async {
|
||||
}
|
||||
}
|
||||
return pw.Image(image, width: 100, height: 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final logoWidget = await buildLogoWidget();
|
||||
final logoWidget = await buildLogoWidget();
|
||||
|
||||
// ✅ FONCTION POUR CONSTRUIRE L'EN-TÊTE DYNAMIQUE
|
||||
pw.Widget buildEnteteInfos() {
|
||||
final infosAUtiliser = infosLivraison.isNotEmpty ? infosLivraison : infosLivraisonDefaut;
|
||||
final infosAUtiliser =
|
||||
infosLivraison.isNotEmpty ? infosLivraison : infosLivraisonDefaut;
|
||||
|
||||
return pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
@ -469,7 +479,9 @@ final logoWidget = await buildLogoWidget();
|
||||
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(
|
||||
@ -477,7 +489,9 @@ final logoWidget = await buildLogoWidget();
|
||||
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,
|
||||
),
|
||||
),
|
||||
@ -500,7 +514,8 @@ final logoWidget = await buildLogoWidget();
|
||||
children: [
|
||||
logoWidget,
|
||||
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),
|
||||
buildEnteteInfos(), // ✅ EN-TÊTE DYNAMIQUE ICI
|
||||
],
|
||||
@ -510,9 +525,12 @@ final logoWidget = await buildLogoWidget();
|
||||
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),
|
||||
@ -522,10 +540,13 @@ final logoWidget = await buildLogoWidget();
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -536,7 +557,8 @@ final logoWidget = await buildLogoWidget();
|
||||
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(
|
||||
@ -544,11 +566,20 @@ final logoWidget = await buildLogoWidget();
|
||||
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} ${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} ${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),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -562,7 +593,10 @@ final logoWidget = await buildLogoWidget();
|
||||
children: [
|
||||
// Debug: Afficher le nombre d'articles
|
||||
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),
|
||||
|
||||
// ✅ TABLE SANS CONTRAINTE DE HAUTEUR - Elle s'adapte au contenu
|
||||
@ -577,24 +611,28 @@ final logoWidget = await buildLogoWidget();
|
||||
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(4),
|
||||
child: pw.Text('Désignations', style: boldTextStyle)
|
||||
),
|
||||
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)
|
||||
),
|
||||
child: pw.Text('Qté',
|
||||
style: boldTextStyle,
|
||||
textAlign: pw.TextAlign.center)),
|
||||
pw.Padding(
|
||||
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(
|
||||
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'];
|
||||
|
||||
// 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(
|
||||
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)
|
||||
? const pw.BoxDecoration(
|
||||
color: PdfColors.grey50)
|
||||
: null,
|
||||
children: [
|
||||
// ✅ Colonne Désignations - Plus compacte
|
||||
pw.Padding(
|
||||
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
|
||||
@ -632,27 +675,28 @@ final logoWidget = await buildLogoWidget();
|
||||
'${detail.produitNom ?? 'Produit inconnu'}',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
fontWeight:
|
||||
pw.FontWeight.bold,
|
||||
font: regularFont)),
|
||||
),
|
||||
if (detail.estCadeau)
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 1),
|
||||
padding:
|
||||
const pw.EdgeInsets.symmetric(
|
||||
horizontal: 3,
|
||||
vertical: 1),
|
||||
decoration: pw.BoxDecoration(
|
||||
color: PdfColors.green600,
|
||||
borderRadius: pw.BorderRadius.circular(3),
|
||||
borderRadius:
|
||||
pw.BorderRadius.circular(3),
|
||||
),
|
||||
child: pw.Text(
|
||||
'CADEAU',
|
||||
child: pw.Text('CADEAU',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 6,
|
||||
color: PdfColors.white,
|
||||
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
|
||||
pw.Text(
|
||||
[
|
||||
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),
|
||||
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)
|
||||
if (produit?.ram?.isNotEmpty == true ||
|
||||
produit?.memoireInterne?.isNotEmpty ==
|
||||
true ||
|
||||
produit?.reference?.isNotEmpty ==
|
||||
true)
|
||||
pw.Text(
|
||||
[
|
||||
if (produit?.ram?.isNotEmpty == true) 'RAM: ${produit!.ram}',
|
||||
if (produit?.memoireInterne?.isNotEmpty == true) 'Stockage: ${produit!.memoireInterne}',
|
||||
if (produit?.reference?.isNotEmpty == true) 'Ref: ${produit!.reference}',
|
||||
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),
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8,
|
||||
color: PdfColors.grey600,
|
||||
font: regularFont),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -686,18 +755,17 @@ final logoWidget = await buildLogoWidget();
|
||||
// Colonne Quantité
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(4),
|
||||
child: pw.Text(
|
||||
'${detail.quantite}',
|
||||
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(4),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||
crossAxisAlignment:
|
||||
pw.CrossAxisAlignment.end,
|
||||
mainAxisSize: pw.MainAxisSize.min,
|
||||
children: [
|
||||
if (detail.estCadeau) ...[
|
||||
@ -705,44 +773,37 @@ final logoWidget = await buildLogoWidget();
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)}',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8,
|
||||
decoration: pw.TextDecoration.lineThrough,
|
||||
decoration: pw
|
||||
.TextDecoration.lineThrough,
|
||||
color: PdfColors.grey600,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
pw.Text(
|
||||
'GRATUIT',
|
||||
font: regularFont)),
|
||||
pw.Text('GRATUIT',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 9,
|
||||
color: PdfColors.green700,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
font: regularFont)),
|
||||
] else if (detail.aRemise) ...[
|
||||
pw.Text(
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)}',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8,
|
||||
decoration: pw.TextDecoration.lineThrough,
|
||||
decoration: pw
|
||||
.TextDecoration.lineThrough,
|
||||
color: PdfColors.grey600,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
font: regularFont)),
|
||||
pw.Text(
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal / detail.quantite)}',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: PdfColors.orange700,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
font: regularFont)),
|
||||
] else
|
||||
pw.Text(
|
||||
NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire),
|
||||
style: smallTextStyle
|
||||
),
|
||||
NumberFormat('#,##0', 'fr_FR')
|
||||
.format(detail.prixUnitaire),
|
||||
style: smallTextStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -751,51 +812,45 @@ final logoWidget = await buildLogoWidget();
|
||||
pw.Padding(
|
||||
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(
|
||||
NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal),
|
||||
NumberFormat('#,##0', 'fr_FR')
|
||||
.format(detail.sousTotal),
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8,
|
||||
decoration: pw.TextDecoration.lineThrough,
|
||||
decoration: pw
|
||||
.TextDecoration.lineThrough,
|
||||
color: PdfColors.grey600,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
pw.Text(
|
||||
'GRATUIT',
|
||||
font: regularFont)),
|
||||
pw.Text('GRATUIT',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 9,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.green700,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
font: regularFont)),
|
||||
] else if (detail.aRemise) ...[
|
||||
pw.Text(
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal)}',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 8,
|
||||
decoration: pw.TextDecoration.lineThrough,
|
||||
decoration: pw
|
||||
.TextDecoration.lineThrough,
|
||||
color: PdfColors.grey600,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
font: regularFont)),
|
||||
pw.Text(
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)}',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
font: regularFont
|
||||
)
|
||||
),
|
||||
font: regularFont)),
|
||||
] else
|
||||
pw.Text(
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)}',
|
||||
style: smallTextStyle
|
||||
),
|
||||
style: smallTextStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -825,44 +880,67 @@ final logoWidget = await buildLogoWidget();
|
||||
children: [
|
||||
pw.Text('SOUS-TOTAL:', style: smallTextStyle),
|
||||
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),
|
||||
],
|
||||
|
||||
if (totalRemises > 0) ...[
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.end,
|
||||
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.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),
|
||||
],
|
||||
|
||||
if (totalCadeaux > 0) ...[
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.end,
|
||||
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.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.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('${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(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
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.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, font: regularFont),
|
||||
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, font: regularFont),
|
||||
validateur != null
|
||||
? '${validateur.name} ${validateur.lastName ?? ''}'
|
||||
.trim()
|
||||
: 'N/A',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 9,
|
||||
font: regularFont),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -925,20 +1021,35 @@ final logoWidget = await buildLogoWidget();
|
||||
|
||||
// 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, font: regularFont)),
|
||||
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, font: regularFont)),
|
||||
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),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -989,13 +1100,21 @@ final logoWidget = await buildLogoWidget();
|
||||
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)),
|
||||
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,
|
||||
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.Container(
|
||||
@ -1006,7 +1125,11 @@ final logoWidget = await buildLogoWidget();
|
||||
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)),
|
||||
child: pw.Text('X',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
font: regularFont)),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -1030,7 +1153,7 @@ final logoWidget = await buildLogoWidget();
|
||||
|
||||
// Partager ou ouvrir le fichier
|
||||
await OpenFile.open(file.path);
|
||||
}
|
||||
}
|
||||
//==============================================================
|
||||
|
||||
// Modifiez la méthode _generateInvoice dans GestionCommandesPage
|
||||
@ -1149,11 +1272,13 @@ final logoWidget = await buildLogoWidget();
|
||||
dynamic blobDynamic = logoRaw;
|
||||
bytes = blobDynamic.toBytes();
|
||||
} else {
|
||||
throw Exception("Format de logo non supporté: ${logoRaw.runtimeType}");
|
||||
throw Exception(
|
||||
"Format de logo non supporté: ${logoRaw.runtimeType}");
|
||||
}
|
||||
|
||||
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) {
|
||||
print('Erreur chargement logo BDD: $e');
|
||||
}
|
||||
@ -1162,8 +1287,9 @@ final logoWidget = await buildLogoWidget();
|
||||
}
|
||||
|
||||
final logoWidget = await buildLogoWidget();
|
||||
pw.Widget buildEnteteFactureInfos() {
|
||||
final infosAUtiliser = infosFacture.isNotEmpty ? infosFacture : infosFactureDefaut;
|
||||
pw.Widget buildEnteteFactureInfos() {
|
||||
final infosAUtiliser =
|
||||
infosFacture.isNotEmpty ? infosFacture : infosFactureDefaut;
|
||||
|
||||
return pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
@ -1180,8 +1306,7 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
pw.SizedBox(height: 2), // ajouté en fin de liste
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
@ -1566,7 +1691,8 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
pw.SizedBox(width: 20),
|
||||
pw.Container(
|
||||
width: 80,
|
||||
child: pw.Text('${NumberFormat('#,##0', 'fr_FR').format(sousTotal)}',
|
||||
child: pw.Text(
|
||||
'${NumberFormat('#,##0', 'fr_FR').format(sousTotal)}',
|
||||
style: normalTextStyle,
|
||||
textAlign: pw.TextAlign.right),
|
||||
),
|
||||
@ -2001,7 +2127,6 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
|
||||
Future<void> _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
|
||||
@ -2242,7 +2367,8 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
children: [
|
||||
pw.Text('SOUS-TOTAL:',
|
||||
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)),
|
||||
],
|
||||
),
|
||||
@ -2253,7 +2379,8 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
pw.Text('REMISES:',
|
||||
style: pw.TextStyle(
|
||||
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(
|
||||
fontSize: 8, color: PdfColors.orange)),
|
||||
],
|
||||
@ -2266,7 +2393,8 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
pw.Text('CADEAUX ($nombreCadeaux):',
|
||||
style: pw.TextStyle(
|
||||
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(
|
||||
fontSize: 8, color: PdfColors.green700)),
|
||||
],
|
||||
@ -2282,7 +2410,8 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
pw.Text('TOTAL:',
|
||||
style: pw.TextStyle(
|
||||
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(
|
||||
fontSize: 9, fontWeight: pw.FontWeight.bold)),
|
||||
],
|
||||
@ -2974,7 +3103,8 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
color:
|
||||
Colors.black.withOpacity(0.1),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
@ -2985,8 +3115,10 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
Icons.payment,
|
||||
color: Colors.green.shade600,
|
||||
),
|
||||
onPressed: () => _showPaymentOptions(commande),
|
||||
tooltip: 'Générer le ticket de la commande',
|
||||
onPressed: () =>
|
||||
_showPaymentOptions(commande),
|
||||
tooltip:
|
||||
'Générer le ticket de la commande',
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
@ -3067,8 +3199,8 @@ pw.Widget buildEnteteFactureInfos() {
|
||||
CommandeActions(
|
||||
commande: commande,
|
||||
onStatutChanged: _updateStatut,
|
||||
onGenerateBonLivraison:_generateBon_lifraisonWithPasswordVerification
|
||||
),
|
||||
onGenerateBonLivraison:
|
||||
_generateBon_lifraisonWithPasswordVerification),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -92,7 +92,9 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
final pointDeVenteId = _userController.pointDeVenteId;
|
||||
final commandes = pointDeVenteId == 0
|
||||
? allCommandes
|
||||
: allCommandes.where((cmd) => cmd.pointDeVenteId == pointDeVenteId).toList();
|
||||
: allCommandes
|
||||
.where((cmd) => cmd.pointDeVenteId == pointDeVenteId)
|
||||
.toList();
|
||||
|
||||
setState(() {
|
||||
_commandes.clear();
|
||||
@ -146,7 +148,8 @@ class _HistoriquePageState extends State<HistoriquePage> {
|
||||
_filteredCommandes.clear();
|
||||
|
||||
for (var commande in _commandes) {
|
||||
if (pointDeVenteId != 0 && commande.pointDeVenteId != pointDeVenteId) continue;
|
||||
if (pointDeVenteId != 0 && commande.pointDeVenteId != pointDeVenteId)
|
||||
continue;
|
||||
bool matchesSearch = searchText.isEmpty ||
|
||||
commande.clientNom!.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),
|
||||
),
|
||||
title: Text(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
detail.produitNom ?? 'Produit inconnu',
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.w500),
|
||||
style: const TextStyle(
|
||||
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(
|
||||
'${detail.quantite} x ${NumberFormat('#,##0.00', 'fr_FR').format(detail.prixUnitaire)} MGA',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user