Browse Source

push finale

28062025_02
andrymodeste 4 months ago
parent
commit
c757bdb701
  1. 19
      lib/Components/commandManagementComponents/CommandDetails.dart
  2. 6
      lib/Components/commandManagementComponents/CommandeActions.dart
  3. 7
      lib/Components/commandManagementComponents/DiscountDialog.dart
  4. 5
      lib/Components/commandManagementComponents/PaymentMethodDialog.dart
  5. 5
      lib/Components/newCommandComponents/CadeauDialog.dart
  6. 11
      lib/Components/newCommandComponents/RemiseDialog.dart
  7. 92
      lib/Services/stock_managementDatabase.dart
  8. 6
      lib/Views/Dashboard.dart
  9. 2
      lib/Views/HandleProduct.dart
  10. 298
      lib/Views/commandManagement.dart
  11. 490
      lib/Views/gestion_point_de_vente.dart
  12. 3
      lib/config/DatabaseConfig.dart
  13. 5
      pubspec.yaml

19
lib/Components/commandManagementComponents/CommandDetails.dart

@ -3,6 +3,7 @@
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';
import 'package:intl/intl.dart';
class CommandeDetails extends StatelessWidget { class CommandeDetails extends StatelessWidget {
final Commande commande; final Commande commande;
@ -54,7 +55,7 @@ class CommandeDetails extends StatelessWidget {
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'${(detail.prixFinal / detail.quantite).toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal / detail.quantite)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -65,7 +66,7 @@ class CommandeDetails extends StatelessWidget {
), ),
); );
} else { } else {
return _buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} MGA', isAmount: true); return _buildTableCell('${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)} MGA', isAmount: true);
} }
} }
@ -85,7 +86,7 @@ class CommandeDetails extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
Text( Text(
'-${detail.montantRemise.toStringAsFixed(0)} MGA', '-${NumberFormat('#,##0', 'fr_FR').format(detail.montantRemise)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: Colors.teal.shade700, color: Colors.teal.shade700,
@ -119,7 +120,7 @@ class CommandeDetails extends StatelessWidget {
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
'${detail.prixFinal.toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)} MGA',
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -129,7 +130,7 @@ class CommandeDetails extends StatelessWidget {
), ),
); );
} else { } else {
return _buildTableCell('${detail.prixFinal.toStringAsFixed(2)} MGA', isAmount: true); return _buildTableCell('${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)} MGA', isAmount: true);
} }
} }
@ -197,7 +198,7 @@ class CommandeDetails extends StatelessWidget {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Text( child: Text(
'Économies: ${totalRemises.toStringAsFixed(0)} MGA', 'Économies: ${NumberFormat('#,##0', 'fr_FR').format(totalRemises)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -312,7 +313,7 @@ class CommandeDetails extends StatelessWidget {
), ),
), ),
Text( Text(
'${sousTotal.toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(sousTotal)} MGA',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -343,7 +344,7 @@ class CommandeDetails extends StatelessWidget {
], ],
), ),
Text( Text(
'-${totalRemises.toStringAsFixed(2)} MGA', '-${NumberFormat('#,##0', 'fr_FR').format(totalRemises)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -367,7 +368,7 @@ class CommandeDetails extends StatelessWidget {
), ),
), ),
Text( Text(
'${commande.montantTotal.toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 18, fontSize: 18,

6
lib/Components/commandManagementComponents/CommandeActions.dart

@ -7,13 +7,13 @@ import 'package:youmazgestion/Models/client.dart';
class CommandeActions extends StatelessWidget { class CommandeActions extends StatelessWidget {
final Commande commande; final Commande commande;
final Function(int, StatutCommande) onStatutChanged; final Function(int, StatutCommande) onStatutChanged;
final Function(Commande) onPaymentSelected; final Function(Commande) onGenerateBonLivraison;
const CommandeActions({ const CommandeActions({
required this.commande, required this.commande,
required this.onStatutChanged, required this.onStatutChanged,
required this.onPaymentSelected, required this.onGenerateBonLivraison,
}); });
@ -30,7 +30,7 @@ class CommandeActions extends StatelessWidget {
label: 'Confirmer', label: 'Confirmer',
icon: Icons.check_circle, icon: Icons.check_circle,
color: Colors.blue, color: Colors.blue,
onPressed: () => onPaymentSelected(commande), onPressed: () => onGenerateBonLivraison(commande),
), ),
_buildActionButton( _buildActionButton(
label: 'Annuler', label: 'Annuler',

7
lib/Components/commandManagementComponents/DiscountDialog.dart

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
@ -48,7 +49,7 @@ class _DiscountDialogState extends State<DiscountDialog> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text('Montant original: ${widget.commande.montantTotal.toStringAsFixed(2)} MGA'), Text('Montant original: ${NumberFormat('#,##0', 'fr_FR').format(widget.commande.montantTotal)} MGA'),
const SizedBox(height: 16), const SizedBox(height: 16),
// Choix du type de remise // Choix du type de remise
@ -124,7 +125,7 @@ class _DiscountDialogState extends State<DiscountDialog> {
children: [ children: [
const Text('Montant final:'), const Text('Montant final:'),
Text( Text(
'${_montantFinal.toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(_montantFinal)} MGA',
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 16,
@ -138,7 +139,7 @@ class _DiscountDialogState extends State<DiscountDialog> {
children: [ children: [
const Text('Économie:'), const Text('Économie:'),
Text( Text(
'${(widget.commande.montantTotal - _montantFinal).toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(widget.commande.montantTotal - _montantFinal)} MGA',
style: const TextStyle( style: const TextStyle(
color: Colors.green, color: Colors.green,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

5
lib/Components/commandManagementComponents/PaymentMethodDialog.dart

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/src/snackbar/snackbar.dart'; import 'package:get/get_navigation/src/snackbar/snackbar.dart';
import 'package:intl/intl.dart';
import 'package:youmazgestion/Components/commandManagementComponents/PaymentMethod.dart'; import 'package:youmazgestion/Components/commandManagementComponents/PaymentMethod.dart';
import 'package:youmazgestion/Components/paymentType.dart'; import 'package:youmazgestion/Components/paymentType.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
@ -81,7 +82,7 @@ class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text('Montant à payer:', style: TextStyle(fontWeight: FontWeight.bold)), const Text('Montant à payer:', style: TextStyle(fontWeight: FontWeight.bold)),
Text('${montantFinal.toStringAsFixed(2)} MGA', Text('${NumberFormat('#,##0', 'fr_FR').format(montantFinal)} MGA',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
], ],
), ),
@ -162,7 +163,7 @@ class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA', 'Monnaie à rendre: ${NumberFormat('#,##0', 'fr_FR').format(change)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

5
lib/Components/newCommandComponents/CadeauDialog.dart

@ -2,6 +2,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
import 'package:youmazgestion/Models/produit.dart'; import 'package:youmazgestion/Models/produit.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
@ -149,7 +150,7 @@ class _CadeauDialogState extends State<CadeauDialog> {
), ),
), ),
Text( Text(
'Prix: ${widget.product.price.toStringAsFixed(2)} MGA', 'Prix: ${NumberFormat('#,##0', 'fr_FR').format(widget.product.price)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade600, color: Colors.grey.shade600,
@ -255,7 +256,7 @@ class _CadeauDialogState extends State<CadeauDialog> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Prix normal: ${produit.price.toStringAsFixed(2)} MGA', 'Prix normal: ${NumberFormat('#,##0', 'fr_FR').format(produit.price)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey.shade600, color: Colors.grey.shade600,

11
lib/Components/newCommandComponents/RemiseDialog.dart

@ -1,6 +1,7 @@
// Components/RemiseDialog.dart // Components/RemiseDialog.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
import 'package:youmazgestion/Models/produit.dart'; import 'package:youmazgestion/Models/produit.dart';
@ -115,11 +116,11 @@ class _RemiseDialogState extends State<RemiseDialog> {
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
), ),
Text( Text(
'Prix unitaire: ${widget.prixUnitaire.toStringAsFixed(2)} MGA', 'Prix unitaire: ${NumberFormat('#,##0', 'fr_FR').format(widget.prixUnitaire)} MGA',
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
), ),
Text( Text(
'Sous-total: ${_sousTotal.toStringAsFixed(2)} MGA', 'Sous-total: ${NumberFormat('#,##0', 'fr_FR').format(_sousTotal)} MGA',
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -234,7 +235,7 @@ class _RemiseDialogState extends State<RemiseDialog> {
children: [ children: [
const Text('Sous-total:', style: TextStyle(fontSize: 12)), const Text('Sous-total:', style: TextStyle(fontSize: 12)),
Text( Text(
'${_sousTotal.toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(_sousTotal)} MGA',
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
), ),
], ],
@ -252,7 +253,7 @@ class _RemiseDialogState extends State<RemiseDialog> {
), ),
), ),
Text( Text(
'-${_montantRemise.toStringAsFixed(2)} MGA', '-${NumberFormat('#,##0', 'fr_FR').format(_montantRemise)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.orange.shade700, color: Colors.orange.shade700,
@ -274,7 +275,7 @@ class _RemiseDialogState extends State<RemiseDialog> {
), ),
), ),
Text( Text(
'${_prixFinal.toStringAsFixed(2)} MGA', '${NumberFormat('#,##0', 'fr_FR').format(_prixFinal)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

92
lib/Services/stock_managementDatabase.dart

@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:math' as console; import 'dart:math' as console;
import 'dart:typed_data';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mysql1/mysql1.dart'; import 'package:mysql1/mysql1.dart';
import 'package:youmazgestion/controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
@ -596,6 +598,7 @@ String _formatDate(DateTime date) {
await insertDefaultPointsDeVente(); await insertDefaultPointsDeVente();
final newResult = final newResult =
await db.query('SELECT * FROM points_de_vente ORDER BY nom ASC'); await db.query('SELECT * FROM points_de_vente ORDER BY nom ASC');
print(newResult);
return newResult.map((row) => row.fields).toList(); return newResult.map((row) => row.fields).toList();
} }
@ -1023,7 +1026,7 @@ 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 id, nom 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();
} }
@ -1238,6 +1241,7 @@ 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]);
@ -1425,12 +1429,53 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
} }
} }
Future<Map<String, dynamic>?> getPointDeVenteById(int id) async { Future<ResultRow?> getPointDeVenteById(int id) async {
final db = await database; final db = await database;
final result = final result =
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.fields : null; return result.isNotEmpty ? result.first : null;
}
List<String> parseHeaderInfo(dynamic blobData) {
if (blobData == null) return [];
try {
String content = '';
print("=== TYPE DE DONNÉES BLOB === ${blobData.runtimeType}");
if (blobData is String) {
content = blobData;
} else if (blobData is Uint8List || blobData is List<int>) {
try {
content = utf8.decode(blobData);
} catch (eUtf8) {
print('❌ utf8.decode failed: $eUtf8');
try {
content = latin1.decode(blobData);
} catch (eLatin1) {
print('❌ latin1.decode failed: $eLatin1');
content = String.fromCharCodes(blobData);
} }
}
} else {
content = blobData.toString();
}
print('=== LIVRAISON BRUTE ===\n$content\n=== FIN ===');
return content
.split('\n')
.map((line) => line.trim())
.where((line) => line.isNotEmpty)
.toList();
} catch (e) {
print('❌ Erreur lors du parsing des données d\'en-tête: $e');
return [];
}
}
Future<int?> getOrCreatePointDeVenteByNom(String nom) async { Future<int?> getOrCreatePointDeVenteByNom(String nom) async {
final db = await database; final db = await database;
@ -3378,6 +3423,47 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
return []; return [];
} }
} }
Future<void> updatePointDeVentes(
int id,
String nom,
String code, {
String? content,
String? livraison,
String? facture,
Uint8List? imagePath,
}) async {
final db = await database;
try {
await db.query(
'''
UPDATE points_de_vente
SET nom = ?,
content = ?,
livraison = ?,
facture = ?,
logo = ?
WHERE id = ?
''',
[
nom,
(content?.isEmpty ?? true) ? null : content,
(livraison?.isEmpty ?? true) ? null : livraison,
(facture?.isEmpty ?? true) ? null : facture,
imagePath,
id,
],
);
} catch (e, stacktrace) {
print('Erreur lors de la mise à jour du point de vente : $e');
print('Stacktrace : $stacktrace');
rethrow; // si tu veux faire remonter lerreur plus haut
}
}
Future<Map<String, dynamic>> getStatistiquesSortiesPersonnelles() async { Future<Map<String, dynamic>> getStatistiquesSortiesPersonnelles() async {
final db = await database; final db = await database;

6
lib/Views/Dashboard.dart

@ -1422,12 +1422,12 @@ Widget _buildTableauVentesPointDeVente(List<Map<String, dynamic>> ventesData) {
), ),
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child:Text(
NumberFormat('#,##0.00', 'fr_FR').format( NumberFormat('#,##0.00', 'fr_FR').format(
(data['chiffre_affaires'] as num?)?.toDouble() ?? 0.0, (data['chiffre_affaires'] as num?)?.toDouble() ?? 0.0,
), ),
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
), )
), ),
Expanded( Expanded(
flex: 1, flex: 1,

2
lib/Views/HandleProduct.dart

@ -4800,6 +4800,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (_userController.username == 'superadmin'|| _userController.username == 'admin') ...[
FloatingActionButton( FloatingActionButton(
heroTag: 'importBtn', heroTag: 'importBtn',
onPressed: _isImporting ? null : _importFromExcel, onPressed: _isImporting ? null : _importFromExcel,
@ -4808,6 +4809,7 @@ Future<void> _showDemandeTransfertDialog(Product product) async {
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
],
const SizedBox(height: 8), const SizedBox(height: 8),
FloatingActionButton.extended( FloatingActionButton.extended(
heroTag: 'addBtn', heroTag: 'addBtn',

298
lib/Views/commandManagement.dart

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mysql1/mysql1.dart';
import 'package:numbers_to_letters/numbers_to_letters.dart'; import 'package:numbers_to_letters/numbers_to_letters.dart';
import 'package:pdf/pdf.dart'; import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw; import 'package:pdf/widgets.dart' as pw;
@ -276,10 +277,22 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
/// Le PDF est sauvegardé dans un fichier temporaire, qui est partagé /// Le PDF est sauvegardé dans un fichier temporaire, qui est partagé
/// via le mécanisme de partage de fichiers du système. /// via le mécanisme de partage de fichiers du système.
/// ///
Future<void> _generateBonLivraison(Commande commande) async { // Dans GestionCommandesPage - Remplacez la méthode _generateBonLivraison complète
Future<void> _generateBonLivraison(Commande commande) 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 pointDeVente = await _database.getPointDeVenteById(1); final pointDeVenteId = commande.pointDeVenteId;
ResultRow? pointDeVenteComplet;
// MODIFICATION: Récupération complète des données du point de vente
if (pointDeVenteId != null) {
pointDeVenteComplet = await _database.getPointDeVenteById(pointDeVenteId);
} else {
print("ce point de vente n'existe pas");
}
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
@ -289,6 +302,32 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
? await _database.getUserById(commande.validateurId!) ? await _database.getUserById(commande.validateurId!)
: null; : null;
// NOUVELLE FONCTIONNALITÉ: Parser les informations d'en-tête pour livraison
List<String> infosLivraison = [];
final livraisonBrute = pointDeVenteComplet?['livraison'];
print('=== LIVRAISON BRUTE ===');
print(livraisonBrute);
print('=== FIN ===');
if (livraisonBrute != null) {
infosLivraison = _database.parseHeaderInfo(livraisonBrute);
print('=== INFOS LIVRAISON PARSÉES ===');
for (int i = 0; i < infosLivraison.length; i++) {
print('Ligne $i: ${infosLivraison[i]}');
}
print('===============================');
}
// Infos par défaut si aucune info personnalisée
final infosLivraisonDefaut = [
'REMAX Andravoangy',
'SUPREME CENTER Behoririka \n BOX 405 | 416 | 119',
'Tripolisa analankely BOX 7',
'033 37 808 18',
'www.guycom.mg',
];
// DEBUG: Vérifiez combien de détails vous avez // DEBUG: Vérifiez combien de détails vous avez
print('=== DEBUG BON DE LIVRAISON ==='); print('=== DEBUG BON DE LIVRAISON ===');
print('Nombre de détails récupérés: ${details.length}'); print('Nombre de détails récupérés: ${details.length}');
@ -329,16 +368,14 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
}); });
print(' ✅ Produit trouvé: ${produit.name}'); print(' ✅ Produit trouvé: ${produit.name}');
} else { } else {
// Même si le produit est null, on l'ajoute quand même avec les infos du détail
detailsAvecProduits.add({ detailsAvecProduits.add({
'detail': detail, 'detail': detail,
'produit': null, // On garde null mais on utilisera les infos du détail 'produit': null,
}); });
print(' ⚠️ Produit non trouvé, utilisation des données du détail'); print(' ⚠️ Produit non trouvé, utilisation des données du détail');
} }
} catch (e) { } catch (e) {
print(' ❌ Erreur lors de la récupération du produit: $e'); print(' ❌ Erreur lors de la récupération du produit: $e');
// En cas d'erreur, on ajoute quand même le détail
detailsAvecProduits.add({ detailsAvecProduits.add({
'detail': detail, 'detail': detail,
'produit': null, 'produit': null,
@ -361,10 +398,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
regularFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Regular.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');
// Utiliser les polices par défaut
} }
// DÉFINITION DES STYLES DE TEXTE - Variables globales dans la fonction // DÉFINITION DES STYLES DE TEXTE
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);
@ -373,11 +409,54 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
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(fontSize: 9, fontWeight: pw.FontWeight.bold, font: italicFont ?? regularFont);
final italicLogoStyle = pw.TextStyle(fontSize: 8, 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'];
// Fonction pour créer un exemplaire - CORRIGÉE if (logoRaw != null) {
try {
Uint8List bytes;
if (logoRaw is Uint8List) {
bytes = logoRaw;
} else if (logoRaw is List<int>) {
bytes = Uint8List.fromList(logoRaw);
} else if (logoRaw.runtimeType.toString() == 'Blob') {
// Cast dynamique pour appeler toBytes()
dynamic blobDynamic = logoRaw;
bytes = blobDynamic.toBytes();
} else {
throw Exception("Format de logo non supporté: ${logoRaw.runtimeType}");
}
final imageLogo = pw.MemoryImage(bytes);
return pw.Image(imageLogo, width: 100, height: 100);
} catch (e) {
print('Erreur chargement logo BDD: $e');
}
}
return pw.Image(image, width: 100, height: 100);
}
final logoWidget = await buildLogoWidget();
// FONCTION POUR CONSTRUIRE L'EN-TÊTE DYNAMIQUE
pw.Widget buildEnteteInfos() {
final infosAUtiliser = infosLivraison.isNotEmpty ? infosLivraison : infosLivraisonDefaut;
return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: infosAUtiliser.map((info) {
return pw.Padding(
padding: const pw.EdgeInsets.only(bottom: 1),
child: pw.Text(info, style: tinyTextStyle),
);
}).toList(),
);
}
// Fonction pour créer un exemplaire - AVEC EN-TÊTE DYNAMIQUE
pw.Widget buildExemplaire(String typeExemplaire) { pw.Widget buildExemplaire(String typeExemplaire) {
return pw.Container( return pw.Container(
// PAS DE HAUTEUR FIXE - Elle s'adapte au contenu
width: double.infinity, width: double.infinity,
decoration: pw.BoxDecoration( decoration: pw.BoxDecoration(
border: pw.Border.all(color: PdfColors.black, width: 1.5), border: pw.Border.all(color: PdfColors.black, width: 1.5),
@ -415,28 +494,15 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [ children: [
// Logo et infos entreprise // Logo et infos entreprise - AVEC INFOS DYNAMIQUES
pw.Column( pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
pw.Container( logoWidget,
width: 100,
height: 100,
child: pw.Image(image),
),
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),
pw.Column( buildEnteteInfos(), // EN-TÊTE DYNAMIQUE ICI
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text('REMAX Andravoangy', style: tinyTextStyle),
pw.Text('SUPREME CENTER Behoririka \n BOX 405 | 416 | 119', style: tinyTextStyle),
pw.Text('Tripolisa analankely BOX 7', style: tinyTextStyle),
pw.Text('033 37 808 18', style: tinyTextStyle),
pw.Text('www.guycom.mg', style: tinyTextStyle),
],
),
], ],
), ),
@ -964,14 +1030,22 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
// Partager ou ouvrir le fichier // Partager ou ouvrir le fichier
await OpenFile.open(file.path); await OpenFile.open(file.path);
} }
//============================================================== //==============================================================
// Modifiez la méthode _generateInvoice dans GestionCommandesPage // Modifiez la méthode _generateInvoice dans GestionCommandesPage
Future<void> _generateInvoice(Commande commande) async { Future<void> _generateInvoice(Commande commande) 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 pointDeVente = await _database.getPointDeVenteById(1); final pointDeVenteId = commande.pointDeVenteId;
ResultRow? pointDeVenteComplet;
if (pointDeVenteId != null) {
pointDeVenteComplet = await _database.getPointDeVenteById(pointDeVenteId);
} else {
print("ce point de vente n'existe pas");
}
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
@ -981,6 +1055,34 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
? await _database.getUserById(commande.validateurId!) ? await _database.getUserById(commande.validateurId!)
: null; : null;
List<String> infosFacture = [];
final factureBrute = pointDeVenteComplet?['facture'];
print('=== FACTURE BRUTE ===');
print(factureBrute);
print('=== FIN ===');
if (factureBrute != null) {
infosFacture = _database.parseHeaderInfo(factureBrute);
print('=== INFOS FACTURE PARSÉES ===');
for (int i = 0; i < infosFacture.length; i++) {
print('Ligne $i: ${infosFacture[i]}');
}
print('===============================');
}
// Infos par défaut si aucune info personnalisée
final infosFactureDefaut = [
'REMAX by GUYCOM Andravoangy',
'SUPREME CENTER Behoririka box 405',
'SUPREME CENTER Behoririka box 416',
'SUPREME CENTER Behoririka box 119',
'TRIPOLITSA Analakely BOX 7',
'033 37 808 18',
'www.guycom.mg',
'NIF: 4000106673 - STAT 95210 11 2017 1 003651',
'Facebook: GuyCom',
];
final iconPhone = await buildIconPhoneText(); final iconPhone = await buildIconPhoneText();
final iconChecked = await buildIconCheckedText(); final iconChecked = await buildIconCheckedText();
final iconGlobe = await buildIconGlobeText(); final iconGlobe = await buildIconGlobeText();
@ -1032,6 +1134,55 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
final emojifont = pw.TextStyle( final emojifont = pw.TextStyle(
fontSize: 8, fontWeight: pw.FontWeight.bold, font: emojiSuportFont); fontSize: 8, fontWeight: pw.FontWeight.bold, font: emojiSuportFont);
Future<pw.Widget> buildLogoWidget() async {
final logoRaw = pointDeVenteComplet?['logo'];
if (logoRaw != null) {
try {
Uint8List bytes;
if (logoRaw is Uint8List) {
bytes = logoRaw;
} else if (logoRaw is List<int>) {
bytes = Uint8List.fromList(logoRaw);
} else if (logoRaw.runtimeType.toString() == 'Blob') {
// Cast dynamique pour appeler toBytes()
dynamic blobDynamic = logoRaw;
bytes = blobDynamic.toBytes();
} else {
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));
} catch (e) {
print('Erreur chargement logo BDD: $e');
}
}
return pw.Container(width: 200, height: 100, child: pw.Image(image));
}
final logoWidget = await buildLogoWidget();
pw.Widget buildEnteteFactureInfos() {
final infosAUtiliser = infosFacture.isNotEmpty ? infosFacture : infosFactureDefaut;
return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
...infosAUtiliser.map((info) {
return pw.Row(
children: [
iconChecked,
pw.SizedBox(width: 4),
pw.Text(info, style: smallTextStyle),
],
);
}),
pw.SizedBox(height: 2), // ajouté en fin de liste
],
);
}
pdf.addPage( pdf.addPage(
pw.Page( pw.Page(
pageFormat: PdfPageFormat.a4, // Mode portrait pageFormat: PdfPageFormat.a4, // Mode portrait
@ -1049,71 +1200,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
pw.Column( pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
pw.Container( logoWidget,
width: 200,
height: 120,
child: pw.Image(image),
),
pw.Text(' NOTRE COMPETENCE, A VOTRE SERVICE', pw.Text(' NOTRE COMPETENCE, A VOTRE SERVICE',
style: italicTextStyleLogo), style: italicTextStyleLogo),
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Column( buildEnteteFactureInfos(),
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Row(children: [
iconChecked,
pw.SizedBox(width: 4),
pw.Text('REMAX by GUYCOM Andravoangy',
style: smallTextStyle)
]),
pw.SizedBox(height: 2),
pw.Row(children: [
iconChecked,
pw.SizedBox(width: 4),
pw.Text('SUPREME CENTER Behoririka box 405',
style: smallTextStyle)
]),
pw.SizedBox(height: 2),
pw.Row(children: [
iconChecked,
pw.SizedBox(width: 4),
pw.Text('SUPREME CENTER Behoririka box 416',
style: smallTextStyle)
]),
pw.SizedBox(height: 2),
pw.Row(children: [
iconChecked,
pw.SizedBox(width: 4),
pw.Text('SUPREME CENTER Behoririka box 119',
style: smallTextStyle)
]),
pw.SizedBox(height: 2),
pw.Row(children: [
iconChecked,
pw.SizedBox(width: 4),
pw.Text('TRIPOLITSA Analakely BOX 7',
style: smallTextStyle)
]),
],
),
pw.SizedBox(height: 8),
pw.Row(children: [
iconPhone,
pw.SizedBox(width: 4),
pw.Text('033 37 808 18', style: smallTextStyle)
]),
pw.Row(children: [
iconGlobe,
pw.SizedBox(width: 4),
pw.Text('www.guycom.mg', style: smallTextStyle)
]),
pw.Row(children: [
iconGlobe,
pw.SizedBox(width: 4),
pw.Text('NIF: 4000106673 - STAT 95210 11 2017 1 003651',
style: smallTextStyle)
]),
pw.Text('Facebook: GuyCom', style: smallTextStyle),
], ],
), ),
@ -1906,9 +1997,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
return 'MÉTHODE INCONNUE (${payment.type.toString()})'; // Debug info return 'MÉTHODE INCONNUE (${payment.type.toString()})'; // Debug info
} }
} }
// Dans GestionCommandesPage - Remplacez la méthode _generateReceipt complète
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
@ -2875,6 +2968,30 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
),
child: IconButton(
icon: Icon(
Icons.payment,
color: Colors.green.shade600,
),
onPressed: () => _showPaymentOptions(commande),
tooltip: 'Générer le ticket de la commande',
),
),
const SizedBox(
width: 10,
),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
@ -2950,8 +3067,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
CommandeActions( CommandeActions(
commande: commande, commande: commande,
onStatutChanged: _updateStatut, onStatutChanged: _updateStatut,
onPaymentSelected: onGenerateBonLivraison:_generateBon_lifraisonWithPasswordVerification
_showPaymentOptions,
), ),
], ],
), ),

490
lib/Views/gestion_point_de_vente.dart

@ -1,5 +1,9 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Components/appDrawer.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
@ -22,8 +26,10 @@ class _AjoutPointDeVentePageState extends State<AjoutPointDeVentePage> {
// Liste des points de vente // Liste des points de vente
List<Map<String, dynamic>> _pointsDeVente = []; List<Map<String, dynamic>> _pointsDeVente = [];
List<Map<String, dynamic>> _filteredPointsDeVente = [];
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -31,10 +37,12 @@ class _AjoutPointDeVentePageState extends State<AjoutPointDeVentePage> {
_searchController.addListener(_filterPointsDeVente); _searchController.addListener(_filterPointsDeVente);
} }
Future<void> _loadPointsDeVente() async { Future<void> _loadPointsDeVente() async {
if (mounted) {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
}); });
}
try { try {
final points = await _appDatabase.getPointsDeVente(); final points = await _appDatabase.getPointsDeVente();
@ -46,11 +54,15 @@ Future<void> _loadPointsDeVente() async {
point['constraintCount'] = (verification['reasons'] as List).length; point['constraintCount'] = (verification['reasons'] as List).length;
} }
if (mounted) {
setState(() { setState(() {
_pointsDeVente = points; _pointsDeVente = points;
_filteredPointsDeVente = List.from(points);
_isLoading = false; _isLoading = false;
}); });
}
} catch (e) { } catch (e) {
if (mounted) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
@ -62,22 +74,25 @@ Future<void> _loadPointsDeVente() async {
colorText: Colors.white, colorText: Colors.white,
); );
} }
} }
void _filterPointsDeVente() {
final query = _searchController.text.toLowerCase();
if (query.isEmpty) {
_loadPointsDeVente();
return;
} }
void _filterPointsDeVente() {
final query = _searchController.text.toLowerCase().trim();
if (mounted) {
setState(() { setState(() {
_pointsDeVente = _pointsDeVente.where((point) { if (query.isEmpty) {
_filteredPointsDeVente = List.from(_pointsDeVente);
} else {
_filteredPointsDeVente = _pointsDeVente.where((point) {
final nom = point['nom']?.toString().toLowerCase() ?? ''; final nom = point['nom']?.toString().toLowerCase() ?? '';
return nom.contains(query); final code = point['code']?.toString().toLowerCase() ?? '';
return nom.contains(query) || code.contains(query);
}).toList(); }).toList();
}
}); });
} }
}
Future<void> _submitForm() async { Future<void> _submitForm() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
@ -114,26 +129,261 @@ Future<void> _loadPointsDeVente() async {
colorText: Colors.white, colorText: Colors.white,
); );
} finally { } finally {
if (mounted) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
} }
Future<void> _showConstraintDialog(int id, Map<String, dynamic> verificationResult) async { }
Future<void> editPointDeVente(BuildContext context, Map<String, dynamic> pointDeVente) async {
print('=== DIAGNOSTIC DES DONNÉES ===');
print('ID: ${pointDeVente['id']}');
print('Nom: ${pointDeVente['nom']}');
print('Code: ${pointDeVente['code']}');
print('Content (ticket): "${pointDeVente['content']}" (Type: ${pointDeVente['content'].runtimeType})');
print('Livraison: "${pointDeVente['livraison']}" (Type: ${pointDeVente['livraison'].runtimeType})');
print('Facture: "${pointDeVente['facture']}" (Type: ${pointDeVente['facture'].runtimeType})');
print('Logo: ${pointDeVente['logo']?.runtimeType}');
print('Toutes les clés: ${pointDeVente.keys.toList()}');
print('===============================');
final _editFormKey = GlobalKey<FormState>();
final ImagePicker _picker = ImagePicker();
// Fonction helper pour convertir les valeurs en String de manière sûre
String safeStringConversion(dynamic value) {
if (value == null) return '';
if (value is String) return value;
// Vérifier si c'est un type binaire (Uint8List, List<int>, etc.)
if (value is Uint8List || value is List<int> || value is List) {
// Si c'est des données binaires, on retourne une chaîne vide
// car on ne peut pas les convertir en texte lisible
return '';
}
// Pour tous les autres types, essayer la conversion toString()
try {
return value.toString();
} catch (e) {
print('Erreur conversion vers String: $e, type: ${value.runtimeType}');
return '';
}
}
// Initialisation dynamique des contrôleurs avec conversion sécurisée
final _editNomController = TextEditingController(
text: safeStringConversion(pointDeVente['nom'])
);
final _editCodeController = TextEditingController(
text: safeStringConversion(pointDeVente['code'])
);
final _editTicketController = TextEditingController(
text: safeStringConversion(pointDeVente['content'])
);
final _editLivraisonController = TextEditingController(
text: safeStringConversion(pointDeVente['livraison'])
);
final _editFactureController = TextEditingController(
text: safeStringConversion(pointDeVente['facture'])
);
File? _selectedImage;
Uint8List? _currentImageBlob;
// Gérer la conversion du logo de manière sécurisée
final logoData = pointDeVente['logo'];
if (logoData != null) {
try {
if (logoData is Uint8List) {
_currentImageBlob = logoData;
} else if (logoData is List<int>) {
_currentImageBlob = Uint8List.fromList(logoData);
} else if (logoData is List) {
// Cas c'est une List<dynamic> contenant des int
_currentImageBlob = Uint8List.fromList(logoData.cast<int>());
} else {
// Type non supporté (comme Blob), laisser null et logger
print('Type de logo non supporté: ${logoData.runtimeType}');
_currentImageBlob = null;
}
} catch (e) {
print('Erreur lors de la conversion du logo: $e');
_currentImageBlob = null;
}
}
bool _isEditLoading = false;
Future<void> pickImage(ImageSource source) async {
try {
final XFile? image = await _picker.pickImage(source: source);
if (image != null) {
_selectedImage = File(image.path);
_currentImageBlob = await _selectedImage!.readAsBytes();
}
} catch (e) {
print('Erreur lors de la sélection de l\'image : $e');
}
}
Future<void> saveChanges() async {
if (_editFormKey.currentState?.validate() ?? false) {
_isEditLoading = true;
try {
await _appDatabase.updatePointDeVentes(
pointDeVente['id'],
_editNomController.text.trim(),
_editCodeController.text.trim(),
content: _editTicketController.text.trim(),
livraison: _editLivraisonController.text.trim(),
facture: _editFactureController.text.trim(),
imagePath: _currentImageBlob,
);
Get.back(); // Fermer le dialog
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Modification enregistrée avec succès')),
);
await _loadPointsDeVente(); // Rafraîchir la liste
} catch (e) {
print('Erreur lors de la sauvegarde : $e');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Erreur lors de la modification')),
);
} finally {
_isEditLoading = false;
}
}
}
// Affichage de la boîte de dialogue
await Get.dialog(
Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 500, maxHeight: 700),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _editFormKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Modifier le point de vente', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
TextFormField(
controller: _editNomController,
decoration: const InputDecoration(labelText: 'Nom du point de vente'),
validator: (value) => value == null || value.isEmpty ? 'Le nom est requis' : null,
),
const SizedBox(height: 8),
TextFormField(
controller: _editCodeController,
decoration: const InputDecoration(labelText: 'Code du point de vente'),
),
const SizedBox(height: 8),
TextFormField(
controller: _editTicketController,
decoration: const InputDecoration(labelText: 'Info ticket'),
maxLines: 3,
),
const SizedBox(height: 8),
TextFormField(
controller: _editLivraisonController,
decoration: const InputDecoration(labelText: 'Info bon de livraison'),
maxLines: 3,
),
const SizedBox(height: 8),
TextFormField(
controller: _editFactureController,
decoration: const InputDecoration(labelText: 'Info facture'),
maxLines: 3,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () => pickImage(ImageSource.gallery),
icon: const Icon(Icons.image),
label: const Text('Galerie'),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton.icon(
onPressed: () => pickImage(ImageSource.camera),
icon: const Icon(Icons.camera_alt),
label: const Text('Caméra'),
),
),
],
),
const SizedBox(height: 10),
if (_currentImageBlob != null)
Container(
height: 100,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.memory(
_currentImageBlob!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) =>
const Center(child: Text('Erreur image')),
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isEditLoading ? null : saveChanges,
icon: const Icon(Icons.save),
label: Text(_isEditLoading ? 'Sauvegarde...' : 'Enregistrer'),
),
),
],
),
),
),
),
),
),
);
// Nettoyage
await Future.delayed(const Duration(milliseconds: 200));
_editNomController.dispose();
_editCodeController.dispose();
_editTicketController.dispose();
_editLivraisonController.dispose();
_editFactureController.dispose();
}
Future<void> _showConstraintDialog(int id, Map<String, dynamic> verificationResult) async {
final reasons = verificationResult['reasons'] as List<String>; final reasons = verificationResult['reasons'] as List<String>;
final suggestions = verificationResult['suggestions'] as List<String>; final suggestions = verificationResult['suggestions'] as List<String>;
await Get.dialog( await Get.dialog(
AlertDialog( AlertDialog(
title: Row( title: const Row(
children: const [ children: [
Icon(Icons.warning, color: Colors.orange), Icon(Icons.warning, color: Colors.orange),
SizedBox(width: 8), SizedBox(width: 8),
Text('Suppression impossible'), Expanded(child: Text('Suppression impossible')),
], ],
), ),
content: SingleChildScrollView( content: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 400),
child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -174,6 +424,7 @@ Future<void> _showConstraintDialog(int id, Map<String, dynamic> verificationResu
], ],
), ),
), ),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
@ -189,13 +440,14 @@ Future<void> _showConstraintDialog(int id, Map<String, dynamic> verificationResu
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
child: const Text('Transférer les produits'), child: const Text('Transférer'),
), ),
], ],
), ),
); );
} }
Future<void> _showTransferDialog(int sourcePointDeVenteId) async {
Future<void> _showTransferDialog(int sourcePointDeVenteId) async {
final pointsDeVente = await _appDatabase.getPointsDeVenteForTransfer(sourcePointDeVenteId); final pointsDeVente = await _appDatabase.getPointsDeVenteForTransfer(sourcePointDeVenteId);
if (pointsDeVente.isEmpty) { if (pointsDeVente.isEmpty) {
@ -214,31 +466,35 @@ Future<void> _showTransferDialog(int sourcePointDeVenteId) async {
await Get.dialog( await Get.dialog(
AlertDialog( AlertDialog(
title: const Text('Transférer les produits'), title: const Text('Transférer les produits'),
content: Column( content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 300),
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Sélectionnez le point de vente de destination pour les produits :'), const Text('Sélectionnez le point de vente de destination pour les produits :'),
const SizedBox(height: 16), const SizedBox(height: 16),
SizedBox( DropdownButtonFormField<int>(
width: double.maxFinite,
child: DropdownButtonFormField<int>(
value: selectedPointDeVenteId, value: selectedPointDeVenteId,
isExpanded: true,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Point de vente de destination', labelText: 'Point de vente de destination',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
items: pointsDeVente.map((pv) => DropdownMenuItem<int>( items: pointsDeVente.map((pv) => DropdownMenuItem<int>(
value: pv['id'] as int, value: pv['id'] as int,
child: Text(pv['nom'] as String), child: Text(
pv['nom'] as String,
overflow: TextOverflow.ellipsis,
),
)).toList(), )).toList(),
onChanged: (value) { onChanged: (value) {
selectedPointDeVenteId = value; selectedPointDeVenteId = value;
}, },
), ),
),
], ],
), ),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
@ -263,20 +519,21 @@ Future<void> _showTransferDialog(int sourcePointDeVenteId) async {
backgroundColor: Colors.green, backgroundColor: Colors.green,
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
child: const Text('Transférer et supprimer'), child: const Text('Transférer'),
), ),
], ],
), ),
); );
} }
// Nouvelle méthode pour effectuer le transfert et la suppression
Future<void> _performTransferAndDelete(int sourceId, int targetId) async { Future<void> _performTransferAndDelete(int sourceId, int targetId) async {
if (mounted) {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
}); });
}
try { try {
// Afficher un dialog de confirmation final
final confirmed = await Get.dialog<bool>( final confirmed = await Get.dialog<bool>(
AlertDialog( AlertDialog(
title: const Text('Confirmation finale'), title: const Text('Confirmation finale'),
@ -324,38 +581,97 @@ Future<void> _performTransferAndDelete(int sourceId, int targetId) async {
colorText: Colors.white, colorText: Colors.white,
); );
} finally { } finally {
if (mounted) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
// Vous pouvez aussi ajouter une méthode pour voir les détails d'un point de vente }
Future<void> _showPointDeVenteDetails(Map<String, dynamic> pointDeVente) async {
Future<void> _showPointDeVenteDetails(Map<String, dynamic> pointDeVente) async {
final id = pointDeVente['id'] as int; final id = pointDeVente['id'] as int;
try { try {
// Récupérer les statistiques
final stats = await _getPointDeVenteStats(id); final stats = await _getPointDeVenteStats(id);
await Get.dialog( await Get.dialog(
AlertDialog( AlertDialog(
title: Text('Détails: ${pointDeVente['nom']}'), title: Text(
content: SingleChildScrollView( 'Détails: ${pointDeVente['nom']}',
style: const TextStyle(fontSize: 16),
),
content: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400,
maxHeight: 500,
),
child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (pointDeVente['logo'] != null)
Container(
height: 150,
width: double.infinity,
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.memory(
pointDeVente['logo'] as Uint8List,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey.shade200,
child: const Center(
child: Text('Image non disponible'),
),
);
},
),
),
),
_buildStatRow('Produits associés', '${stats['produits']}'), _buildStatRow('Produits associés', '${stats['produits']}'),
_buildStatRow('Utilisateurs associés', '${stats['utilisateurs']}'), _buildStatRow('Utilisateurs associés', '${stats['utilisateurs']}'),
_buildStatRow('Demandes de transfert', '${stats['transferts']}'), _buildStatRow('Demandes de transfert', '${stats['transferts']}'),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( if (pointDeVente['content'] != null &&
'Code: ${pointDeVente['code'] ?? 'N/A'}', pointDeVente['content'].toString().isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'Ticket: ${pointDeVente['content']}',
style: const TextStyle(fontSize: 12, color: Colors.grey), style: const TextStyle(fontSize: 12, color: Colors.grey),
), ),
),
if (pointDeVente['livraison'] != null &&
pointDeVente['livraison'].toString().isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'Bon de livraison: ${pointDeVente['livraison']}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
),
if (pointDeVente['facture'] != null &&
pointDeVente['facture'].toString().isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'Facture: ${pointDeVente['facture']}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
),
], ],
), ),
), ),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
@ -373,23 +689,30 @@ Future<void> _showPointDeVenteDetails(Map<String, dynamic> pointDeVente) async {
colorText: Colors.white, colorText: Colors.white,
); );
} }
} }
Widget _buildStatRow(String label, String value) { Widget _buildStatRow(String label, String value) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 2), padding: const EdgeInsets.symmetric(vertical: 2),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(label), Expanded(
Text(value, style: const TextStyle(fontWeight: FontWeight.bold)), child: Text(
label,
style: const TextStyle(fontSize: 13),
),
),
Text(
value,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
),
], ],
), ),
); );
} }
// Méthode helper pour récupérer les stats Future<Map<String, int>> _getPointDeVenteStats(int id) async {
Future<Map<String, int>> _getPointDeVenteStats(int id) async {
final verification = await _appDatabase.checkCanDeletePointDeVente(id); final verification = await _appDatabase.checkCanDeletePointDeVente(id);
// Parser les raisons pour extraire les nombres // Parser les raisons pour extraire les nombres
@ -410,19 +733,16 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
'utilisateurs': utilisateurs, 'utilisateurs': utilisateurs,
'transferts': transferts, 'transferts': transferts,
}; };
} }
Future<void> _deletePointDeVente(int id) async { Future<void> _deletePointDeVente(int id) async {
// 1. D'abord vérifier si la suppression est possible
final verificationResult = await _appDatabase.checkCanDeletePointDeVente(id); final verificationResult = await _appDatabase.checkCanDeletePointDeVente(id);
if (!verificationResult['canDelete']) { if (!verificationResult['canDelete']) {
// Afficher un dialog avec les détails des contraintes
await _showConstraintDialog(id, verificationResult); await _showConstraintDialog(id, verificationResult);
return; return;
} }
// 2. Si pas de contraintes, procéder normalement
final confirmed = await Get.dialog<bool>( final confirmed = await Get.dialog<bool>(
AlertDialog( AlertDialog(
title: const Text('Confirmer la suppression'), title: const Text('Confirmer la suppression'),
@ -441,9 +761,11 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
); );
if (confirmed == true) { if (confirmed == true) {
if (mounted) {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
}); });
}
try { try {
await _appDatabase.deletePointDeVente(id); await _appDatabase.deletePointDeVente(id);
@ -465,12 +787,14 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
colorText: Colors.white, colorText: Colors.white,
); );
} finally { } finally {
if (mounted) {
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
} }
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -503,7 +827,6 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Champ Nom
TextFormField( TextFormField(
controller: _nomController, controller: _nomController,
decoration: InputDecoration( decoration: InputDecoration(
@ -524,7 +847,6 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Champ Code
TextFormField( TextFormField(
controller: _codeController, controller: _codeController,
decoration: InputDecoration( decoration: InputDecoration(
@ -539,10 +861,8 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Bouton de soumission
ElevatedButton( ElevatedButton(
onPressed: _isLoading ? null : _submitForm, onPressed: _isLoading ? null : _submitForm,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blue.shade800, backgroundColor: Colors.blue.shade800,
@ -579,7 +899,7 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
children: [ children: [
// Barre de recherche // Barre de recherche
Padding( Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 16),
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: InputDecoration(
@ -595,7 +915,7 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () { onPressed: () {
_searchController.clear(); _searchController.clear();
_loadPointsDeVente(); _filterPointsDeVente();
}, },
) )
: null, : null,
@ -603,49 +923,11 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
), ),
), ),
// En-tête de liste
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
const Expanded(
flex: 2,
child: Text(
'Nom',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 9, 56, 95),
),
),
),
const Expanded(
child: Text(
'Code',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 9, 56, 95),
),
),
),
SizedBox(
width: 40,
child: Text(
'Actions',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 9, 56, 95),
),
),
),
],
),
),
// Liste // Liste
Expanded( Expanded(
child: _isLoading && _pointsDeVente.isEmpty child: _isLoading && _filteredPointsDeVente.isEmpty
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: _pointsDeVente.isEmpty : _filteredPointsDeVente.isEmpty
? const Center( ? const Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -663,12 +945,11 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
), ),
) )
: ListView.builder( : ListView.builder(
itemCount: _pointsDeVente.length, itemCount: _filteredPointsDeVente.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final point = _pointsDeVente[index]; final point = _filteredPointsDeVente[index];
final canDelete = point['canDelete'] ?? true; final canDelete = point['canDelete'] ?? true;
final constraintCount = point['constraintCount'] ?? 0; final constraintCount = point['constraintCount'] ?? 0;
return Card( return Card(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
elevation: 2, elevation: 2,
@ -791,7 +1072,16 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
onPressed: () => _showPointDeVenteDetails(point), onPressed: () => _showPointDeVenteDetails(point),
tooltip: 'Voir les détails', tooltip: 'Voir les détails',
), ),
// Bouton modifier
IconButton(
icon: Icon(
Icons.edit,
size: 20,
color: Colors.blue.shade600,
),
onPressed: () => editPointDeVente(context, point),
tooltip: 'Modifier point de vente',
),
// Bouton suppression avec indication visuelle // Bouton suppression avec indication visuelle
IconButton( IconButton(
icon: Icon( icon: Icon(
@ -810,7 +1100,7 @@ Future<Map<String, int>> _getPointDeVenteStats(int id) async {
), ),
); );
}, },
) ),
), ),
], ],
), ),

3
lib/config/DatabaseConfig.dart

@ -11,8 +11,7 @@ class DatabaseConfig {
static const String localDatabase = 'guycom'; static const String localDatabase = 'guycom';
// Production (public) MySQL settings // Production (public) MySQL settings
static const String prodHost = '185.70.105.157'; static const String prodHost = '102.17.52.31';
// static const String prodHost = '102.17.52.31';
static const String prodUsername = 'guycom'; static const String prodUsername = 'guycom';
static const String prodPassword = '3iV59wjRdbuXAPR'; static const String prodPassword = '3iV59wjRdbuXAPR';
static const String prodDatabase = 'guycom'; static const String prodDatabase = 'guycom';

5
pubspec.yaml

@ -70,11 +70,6 @@ dependencies:
window_manager: ^0.3.7 window_manager: ^0.3.7
camera: ^0.10.5+9 camera: ^0.10.5+9
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

Loading…
Cancel
Save