import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; class AjoutPointDeVentePage extends StatefulWidget { const AjoutPointDeVentePage({super.key}); @override _AjoutPointDeVentePageState createState() => _AjoutPointDeVentePageState(); } class _AjoutPointDeVentePageState extends State { final AppDatabase _appDatabase = AppDatabase.instance; final _formKey = GlobalKey(); bool _isLoading = false; // Contrôleurs final TextEditingController _nomController = TextEditingController(); final TextEditingController _codeController = TextEditingController(); // Liste des points de vente List> _pointsDeVente = []; final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); _loadPointsDeVente(); _searchController.addListener(_filterPointsDeVente); } Future _loadPointsDeVente() async { setState(() { _isLoading = true; }); try { final points = await _appDatabase.getPointsDeVente(); // Enrichir chaque point de vente avec les informations de contraintes for (var point in points) { final verification = await _appDatabase.checkCanDeletePointDeVente(point['id']); point['canDelete'] = verification['canDelete']; point['constraintCount'] = (verification['reasons'] as List).length; } setState(() { _pointsDeVente = points; _isLoading = false; }); } catch (e) { setState(() { _isLoading = false; }); Get.snackbar( 'Erreur', 'Impossible de charger les points de vente: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } } void _filterPointsDeVente() { final query = _searchController.text.toLowerCase(); if (query.isEmpty) { _loadPointsDeVente(); return; } setState(() { _pointsDeVente = _pointsDeVente.where((point) { final nom = point['nom']?.toString().toLowerCase() ?? ''; return nom.contains(query); }).toList(); }); } Future _submitForm() async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); try { await _appDatabase.createPointDeVente( _nomController.text.trim(), _codeController.text.trim(), ); // Réinitialiser le formulaire _nomController.clear(); _codeController.clear(); // Recharger la liste await _loadPointsDeVente(); Get.snackbar( 'Succès', 'Point de vente ajouté avec succès', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, ); } catch (e) { Get.snackbar( 'Erreur', 'Impossible d\'ajouter le point de vente: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } finally { setState(() { _isLoading = false; }); } } } Future _showConstraintDialog(int id, Map verificationResult) async { final reasons = verificationResult['reasons'] as List; final suggestions = verificationResult['suggestions'] as List; await Get.dialog( AlertDialog( title: Row( children: const [ Icon(Icons.warning, color: Colors.orange), SizedBox(width: 8), Text('Suppression impossible'), ], ), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const Text( 'Ce point de vente ne peut pas être supprimé pour les raisons suivantes :', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 12), ...reasons.map((reason) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('• ', style: TextStyle(color: Colors.red)), Expanded(child: Text(reason)), ], ), )), if (suggestions.isNotEmpty) ...[ const SizedBox(height: 16), const Text( 'Solutions possibles :', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue), ), const SizedBox(height: 8), ...suggestions.map((suggestion) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('💡 ', style: TextStyle(fontSize: 12)), Expanded(child: Text(suggestion, style: const TextStyle(fontSize: 13))), ], ), )), ], ], ), ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text('Fermer'), ), if (reasons.any((r) => r.contains('produit'))) ElevatedButton( onPressed: () { Get.back(); _showTransferDialog(id); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, ), child: const Text('Transférer les produits'), ), ], ), ); } Future _showTransferDialog(int sourcePointDeVenteId) async { final pointsDeVente = await _appDatabase.getPointsDeVenteForTransfer(sourcePointDeVenteId); if (pointsDeVente.isEmpty) { Get.snackbar( 'Erreur', 'Aucun autre point de vente disponible pour le transfert', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); return; } int? selectedPointDeVenteId; await Get.dialog( AlertDialog( title: const Text('Transférer les produits'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Sélectionnez le point de vente de destination pour les produits :'), const SizedBox(height: 16), SizedBox( width: double.maxFinite, child: DropdownButtonFormField( value: selectedPointDeVenteId, decoration: const InputDecoration( labelText: 'Point de vente de destination', border: OutlineInputBorder(), ), items: pointsDeVente.map((pv) => DropdownMenuItem( value: pv['id'] as int, child: Text(pv['nom'] as String), )).toList(), onChanged: (value) { selectedPointDeVenteId = value; }, ), ), ], ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text('Annuler'), ), ElevatedButton( onPressed: () async { if (selectedPointDeVenteId != null) { Get.back(); await _performTransferAndDelete(sourcePointDeVenteId, selectedPointDeVenteId!); } else { Get.snackbar( 'Erreur', 'Veuillez sélectionner un point de vente de destination', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, ), child: const Text('Transférer et supprimer'), ), ], ), ); } // Nouvelle méthode pour effectuer le transfert et la suppression Future _performTransferAndDelete(int sourceId, int targetId) async { setState(() { _isLoading = true; }); try { // Afficher un dialog de confirmation final final confirmed = await Get.dialog( AlertDialog( title: const Text('Confirmation finale'), content: const Text( 'Cette action va transférer tous les produits vers le point de vente sélectionné ' 'puis supprimer définitivement le point de vente original. ' 'Cette action est irréversible. Continuer ?' ), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Get.back(result: true), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('Confirmer'), ), ], ), ); if (confirmed == true) { await _appDatabase.deletePointDeVenteWithTransfer(sourceId, targetId); await _loadPointsDeVente(); Get.snackbar( 'Succès', 'Produits transférés et point de vente supprimé avec succès', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, duration: const Duration(seconds: 4), ); } } catch (e) { Get.snackbar( 'Erreur', 'Erreur lors du transfert: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } finally { setState(() { _isLoading = false; }); } } // Vous pouvez aussi ajouter une méthode pour voir les détails d'un point de vente Future _showPointDeVenteDetails(Map pointDeVente) async { final id = pointDeVente['id'] as int; try { // Récupérer les statistiques final stats = await _getPointDeVenteStats(id); await Get.dialog( AlertDialog( title: Text('Détails: ${pointDeVente['nom']}'), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ _buildStatRow('Produits associés', '${stats['produits']}'), _buildStatRow('Utilisateurs associés', '${stats['utilisateurs']}'), _buildStatRow('Demandes de transfert', '${stats['transferts']}'), const SizedBox(height: 8), Text( 'Code: ${pointDeVente['code'] ?? 'N/A'}', style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text('Fermer'), ), ], ), ); } catch (e) { Get.snackbar( 'Erreur', 'Impossible de récupérer les détails: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } } Widget _buildStatRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label), Text(value, style: const TextStyle(fontWeight: FontWeight.bold)), ], ), ); } // Méthode helper pour récupérer les stats Future> _getPointDeVenteStats(int id) async { final verification = await _appDatabase.checkCanDeletePointDeVente(id); // Parser les raisons pour extraire les nombres int produits = 0, utilisateurs = 0, transferts = 0; for (String reason in verification['reasons']) { if (reason.contains('produit')) { produits = int.tryParse(reason.split(' ')[0]) ?? 0; } else if (reason.contains('utilisateur')) { utilisateurs = int.tryParse(reason.split(' ')[0]) ?? 0; } else if (reason.contains('transfert')) { transferts = int.tryParse(reason.split(' ')[0]) ?? 0; } } return { 'produits': produits, 'utilisateurs': utilisateurs, 'transferts': transferts, }; } Future _deletePointDeVente(int id) async { // 1. D'abord vérifier si la suppression est possible final verificationResult = await _appDatabase.checkCanDeletePointDeVente(id); if (!verificationResult['canDelete']) { // Afficher un dialog avec les détails des contraintes await _showConstraintDialog(id, verificationResult); return; } // 2. Si pas de contraintes, procéder normalement final confirmed = await Get.dialog( AlertDialog( title: const Text('Confirmer la suppression'), content: const Text('Voulez-vous vraiment supprimer ce point de vente ?'), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text('Annuler'), ), TextButton( onPressed: () => Get.back(result: true), child: const Text('Supprimer', style: TextStyle(color: Colors.red)), ), ], ), ); if (confirmed == true) { setState(() { _isLoading = true; }); try { await _appDatabase.deletePointDeVente(id); await _loadPointsDeVente(); Get.snackbar( 'Succès', 'Point de vente supprimé avec succès', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, ); } catch (e) { Get.snackbar( 'Erreur', 'Impossible de supprimer le point de vente: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } finally { setState(() { _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(title: 'Gestion des points de vente'), drawer: CustomDrawer(), body: Column( children: [ // Formulaire d'ajout Card( margin: const EdgeInsets.all(16), elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( 'Ajouter un point de vente', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 9, 56, 95), ), ), const SizedBox(height: 16), // Champ Nom TextFormField( controller: _nomController, decoration: InputDecoration( labelText: 'Nom du point de vente', prefixIcon: const Icon(Icons.store), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer un nom'; } return null; }, ), const SizedBox(height: 12), // Champ Code TextFormField( controller: _codeController, decoration: InputDecoration( labelText: 'Code (optionnel)', prefixIcon: const Icon(Icons.code), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ), const SizedBox(height: 16), // Bouton de soumission ElevatedButton( onPressed: _isLoading ? null : _submitForm, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Text( 'Ajouter le point de vente', style: TextStyle(fontSize: 16), ), ), ], ), ), ), ), // Liste des points de vente Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ // Barre de recherche Padding( padding: const EdgeInsets.only(bottom: 8), child: TextField( controller: _searchController, decoration: InputDecoration( labelText: 'Rechercher un point de vente', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); _loadPointsDeVente(); }, ) : null, ), ), ), // 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 Expanded( child: _isLoading && _pointsDeVente.isEmpty ? const Center(child: CircularProgressIndicator()) : _pointsDeVente.isEmpty ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.store_mall_directory_outlined, size: 60, color: Colors.grey), SizedBox(height: 16), Text( 'Aucun point de vente trouvé', style: TextStyle( fontSize: 16, color: Colors.grey), ), ], ), ) : ListView.builder( itemCount: _pointsDeVente.length, itemBuilder: (context, index) { final point = _pointsDeVente[index]; final canDelete = point['canDelete'] ?? true; final constraintCount = point['constraintCount'] ?? 0; return Card( margin: const EdgeInsets.only(bottom: 8), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: !canDelete ? Border.all( color: Colors.orange.shade300, width: 1, ) : null, ), child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ // Icône avec indicateur de statut Stack( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: canDelete ? Colors.blue.shade50 : Colors.orange.shade50, borderRadius: BorderRadius.circular(6), ), child: Icon( Icons.store, color: canDelete ? Colors.blue.shade700 : Colors.orange.shade700, size: 20, ), ), if (!canDelete) Positioned( right: 0, top: 0, child: Container( padding: const EdgeInsets.all(2), decoration: const BoxDecoration( color: Colors.orange, shape: BoxShape.circle, ), child: Text( '$constraintCount', style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), ], ), const SizedBox(width: 12), // Informations du point de vente Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( point['nom'] ?? 'N/A', style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 15, ), ), ), if (!canDelete) Icon( Icons.link, size: 16, color: Colors.orange.shade600, ), ], ), Row( children: [ if (point['code'] != null && point['code'].toString().isNotEmpty) Text( 'Code: ${point['code']}', style: TextStyle( color: Colors.grey.shade600, fontSize: 12, ), ), if (!canDelete) ...[ const SizedBox(width: 8), Text( '$constraintCount contrainte(s)', style: TextStyle( color: Colors.orange.shade600, fontSize: 11, fontWeight: FontWeight.w500, ), ), ], ], ), ], ), ), // Boutons d'actions Row( mainAxisSize: MainAxisSize.min, children: [ // Bouton détails IconButton( icon: Icon( Icons.info_outline, size: 20, color: Colors.blue.shade600, ), onPressed: () => _showPointDeVenteDetails(point), tooltip: 'Voir les détails', ), // Bouton suppression avec indication visuelle IconButton( icon: Icon( canDelete ? Icons.delete_outline : Icons.delete_forever_outlined, size: 20, color: canDelete ? Colors.red : Colors.orange.shade700, ), onPressed: () => _deletePointDeVente(point['id']), tooltip: canDelete ? 'Supprimer' : 'Supprimer (avec contraintes)', ), ], ), ], ), ), ), ); }, ) ), ], ), ), ), ], ), ); } @override void dispose() { _nomController.dispose(); _codeController.dispose(); _searchController.dispose(); super.dispose(); } }