import 'dart:async'; import 'package:get/get.dart'; import 'package:mysql1/mysql1.dart'; import 'package:youmazgestion/controller/userController.dart'; // Models import '../Models/users.dart'; import '../Models/role.dart'; import '../Models/Permission.dart'; import '../Models/client.dart'; import '../Models/produit.dart'; import '../config/DatabaseConfig.dart'; class AppDatabase { static final AppDatabase instance = AppDatabase._init(); MySqlConnection? _connection; AppDatabase._init(); Future get database async { if (_connection != null) { try { // Test si la connexion est toujours active en exécutant une requête simple await _connection!.query('SELECT 1'); return _connection!; } catch (e) { // Si la requête échoue, la connexion est fermée, on la recrée print("Connexion MySQL fermée, reconnexion..."); _connection = null; } } _connection = await _initDB(); return _connection!; } Future initDatabase() async { _connection = await _initDB(); await _createDB(); // Effectuer la migration pour les bases existantes await migrateDatabaseForDiscountAndGift(); await insertDefaultPermissions(); await insertDefaultMenus(); await insertDefaultRoles(); await insertDefaultSuperAdmin(); await insertDefaultPointsDeVente(); } Future _initDB() async { try { final config = DatabaseConfig.getConfig(); final settings = ConnectionSettings( host: config['host'], port: config['port'], user: config['user'], password: config['password'], db: config['database'], timeout: Duration(seconds: config['timeout']), ); final connection = await MySqlConnection.connect(settings); print("Connexion MySQL établie avec succès !"); return connection; } catch (e) { print("Erreur de connexion MySQL: $e"); rethrow; } } // Méthode mise à jour pour créer les tables avec les nouvelles colonnes Future _createDB() async { final db = await database; try { // Table roles await db.query(''' CREATE TABLE IF NOT EXISTS roles ( id INT AUTO_INCREMENT PRIMARY KEY, designation VARCHAR(255) NOT NULL UNIQUE ) ENGINE=InnoDB '''); // Table permissions await db.query(''' CREATE TABLE IF NOT EXISTS permissions ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE ) ENGINE=InnoDB '''); // Table menu await db.query(''' CREATE TABLE IF NOT EXISTS menu ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, route VARCHAR(255) NOT NULL ) ENGINE=InnoDB '''); // Table role_permissions await db.query(''' CREATE TABLE IF NOT EXISTS role_permissions ( role_id INT, permission_id INT, PRIMARY KEY (role_id, permission_id), FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE ) ENGINE=InnoDB '''); // Table role_menu_permissions await db.query(''' CREATE TABLE IF NOT EXISTS role_menu_permissions ( role_id INT, menu_id INT, permission_id INT, PRIMARY KEY (role_id, menu_id, permission_id), FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, FOREIGN KEY (menu_id) REFERENCES menu(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE ) ENGINE=InnoDB '''); // Table points_de_vente await db.query(''' CREATE TABLE IF NOT EXISTS points_de_vente ( id INT AUTO_INCREMENT PRIMARY KEY, nom VARCHAR(255) NOT NULL UNIQUE ) ENGINE=InnoDB '''); // Table users await db.query(''' CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, lastname VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL UNIQUE, role_id INT NOT NULL, point_de_vente_id INT, FOREIGN KEY (role_id) REFERENCES roles(id), FOREIGN KEY (point_de_vente_id) REFERENCES points_de_vente(id) ) ENGINE=InnoDB '''); // Table products await db.query(''' CREATE TABLE IF NOT EXISTS products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL, image VARCHAR(2000), category VARCHAR(255) NOT NULL, stock INT NOT NULL DEFAULT 0, description VARCHAR(1000), qrCode VARCHAR(500), reference VARCHAR(255), point_de_vente_id INT, marque VARCHAR(255), ram VARCHAR(100), memoire_interne VARCHAR(100), imei VARCHAR(255) UNIQUE, FOREIGN KEY (point_de_vente_id) REFERENCES points_de_vente(id), INDEX idx_products_category (category), INDEX idx_products_reference (reference), INDEX idx_products_imei (imei) ) ENGINE=InnoDB '''); // Table clients await db.query(''' CREATE TABLE IF NOT EXISTS clients ( id INT AUTO_INCREMENT PRIMARY KEY, nom VARCHAR(255) NOT NULL, prenom VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, telephone VARCHAR(255) NOT NULL, adresse VARCHAR(500), dateCreation DATETIME NOT NULL, actif TINYINT(1) NOT NULL DEFAULT 1, INDEX idx_clients_email (email), INDEX idx_clients_telephone (telephone) ) ENGINE=InnoDB '''); // Table commandes MISE À JOUR avec les champs de remise await db.query(''' CREATE TABLE IF NOT EXISTS commandes ( id INT AUTO_INCREMENT PRIMARY KEY, clientId INT NOT NULL, dateCommande DATETIME NOT NULL, statut INT NOT NULL DEFAULT 0, montantTotal DECIMAL(10,2) NOT NULL, notes VARCHAR(1000), dateLivraison DATETIME, commandeurId INT, validateurId INT, remisePourcentage DECIMAL(5,2) NULL, remiseMontant DECIMAL(10,2) NULL, montantApresRemise DECIMAL(10,2) NULL, FOREIGN KEY (commandeurId) REFERENCES users(id), FOREIGN KEY (validateurId) REFERENCES users(id), FOREIGN KEY (clientId) REFERENCES clients(id), INDEX idx_commandes_client (clientId), INDEX idx_commandes_date (dateCommande) ) ENGINE=InnoDB '''); // Table details_commandes MISE À JOUR avec le champ cadeau await db.query(''' CREATE TABLE IF NOT EXISTS details_commandes ( id INT AUTO_INCREMENT PRIMARY KEY, commandeId INT NOT NULL, produitId INT NOT NULL, quantite INT NOT NULL, prixUnitaire DECIMAL(10,2) NOT NULL, sousTotal DECIMAL(10,2) NOT NULL, estCadeau TINYINT(1) DEFAULT 0, FOREIGN KEY (commandeId) REFERENCES commandes(id) ON DELETE CASCADE, FOREIGN KEY (produitId) REFERENCES products(id), INDEX idx_details_commande (commandeId) ) ENGINE=InnoDB '''); print("Tables créées avec succès avec les nouveaux champs !"); } catch (e) { print("Erreur lors de la création des tables: $e"); rethrow; } } // --- MÉTHODES D'INSERTION PAR DÉFAUT --- Future insertDefaultPermissions() async { final db = await database; try { final existing = await db.query('SELECT COUNT(*) as count FROM permissions'); final count = existing.first['count'] as int; if (count == 0) { final permissions = ['view', 'create', 'update', 'delete', 'admin', 'manage', 'read']; for (String permission in permissions) { await db.query('INSERT INTO permissions (name) VALUES (?)', [permission]); } print("Permissions par défaut insérées"); } else { // Vérifier et ajouter les nouvelles permissions si elles n'existent pas final newPermissions = ['manage', 'read']; for (var permission in newPermissions) { final existingPermission = await db.query( 'SELECT COUNT(*) as count FROM permissions WHERE name = ?', [permission] ); final permCount = existingPermission.first['count'] as int; if (permCount == 0) { await db.query('INSERT INTO permissions (name) VALUES (?)', [permission]); print("Permission ajoutée: $permission"); } } } } catch (e) { print("Erreur insertDefaultPermissions: $e"); } } Future insertDefaultMenus() async { final db = await database; try { final existingMenus = await db.query('SELECT COUNT(*) as count FROM menu'); final count = existingMenus.first['count'] as int; if (count == 0) { final menus = [ {'name': 'Accueil', 'route': '/accueil'}, {'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'}, {'name': 'Modifier/Supprimer un utilisateur', 'route': '/modifier-utilisateur'}, {'name': 'Ajouter un produit', 'route': '/ajouter-produit'}, {'name': 'Modifier/Supprimer un produit', 'route': '/modifier-produit'}, {'name': 'Bilan', 'route': '/bilan'}, {'name': 'Gérer les rôles', 'route': '/gerer-roles'}, {'name': 'Gestion de stock', 'route': '/gestion-stock'}, {'name': 'Historique', 'route': '/historique'}, {'name': 'Déconnexion', 'route': '/deconnexion'}, {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'}, {'name': 'Gérer les commandes', 'route': '/gerer-commandes'}, {'name': 'Points de vente', 'route': '/points-de-vente'}, ]; for (var menu in menus) { await db.query( 'INSERT INTO menu (name, route) VALUES (?, ?)', [menu['name'], menu['route']] ); } print("Menus par défaut insérés"); } else { await _addMissingMenus(db); } } catch (e) { print("Erreur insertDefaultMenus: $e"); } } Future insertDefaultRoles() async { final db = await database; try { final existingRoles = await db.query('SELECT COUNT(*) as count FROM roles'); final count = existingRoles.first['count'] as int; if (count == 0) { // Créer les rôles final roles = ['Super Admin', 'Admin', 'User', 'commercial', 'caisse']; Map roleIds = {}; for (String role in roles) { final result = await db.query('INSERT INTO roles (designation) VALUES (?)', [role]); roleIds[role] = result.insertId!; } // Récupérer les permissions et menus final permissions = await db.query('SELECT * FROM permissions'); final menus = await db.query('SELECT * FROM menu'); // Assigner toutes les permissions à tous les menus pour le Super Admin final superAdminRoleId = roleIds['Super Admin']!; for (var menu in menus) { for (var permission in permissions) { await db.query(''' INSERT IGNORE INTO role_menu_permissions (role_id, menu_id, permission_id) VALUES (?, ?, ?) ''', [superAdminRoleId, menu['id'], permission['id']]); } } // Assigner quelques permissions à l'Admin et à l'User await _assignBasicPermissionsToRoles(db, roleIds['Admin']!, roleIds['User']!); print("Rôles par défaut créés et permissions assignées"); } else { await _updateExistingRolePermissions(db); } } catch (e) { print("Erreur insertDefaultRoles: $e"); } } Future insertDefaultPointsDeVente() async { final db = await database; try { final existing = await db.query('SELECT COUNT(*) as count FROM points_de_vente'); final count = existing.first['count'] as int; if (count == 0) { final defaultPoints = ['405A', '405B', '416', 'S405A', '417']; for (var point in defaultPoints) { try { await db.query('INSERT IGNORE INTO points_de_vente (nom) VALUES (?)', [point]); } catch (e) { print("Erreur insertion point de vente $point: $e"); } } print("Points de vente par défaut insérés"); } } catch (e) { print("Erreur insertDefaultPointsDeVente: $e"); } } Future insertDefaultSuperAdmin() async { final db = await database; try { final existingSuperAdmin = await db.query(''' SELECT u.* FROM users u INNER JOIN roles r ON u.role_id = r.id WHERE r.designation = 'Super Admin' '''); if (existingSuperAdmin.isEmpty) { final superAdminRole = await db.query( 'SELECT id FROM roles WHERE designation = ?', ['Super Admin'] ); if (superAdminRole.isNotEmpty) { final superAdminRoleId = superAdminRole.first['id']; await db.query(''' INSERT INTO users (name, lastname, email, password, username, role_id) VALUES (?, ?, ?, ?, ?, ?) ''', [ 'Super', 'Admin', 'superadmin@youmazgestion.com', 'admin123', 'superadmin', superAdminRoleId ]); print("Super Admin créé avec succès !"); print("Username: superadmin"); print("Password: admin123"); print("ATTENTION: Changez ce mot de passe après la première connexion !"); } } else { print("Super Admin existe déjà"); } } catch (e) { print("Erreur insertDefaultSuperAdmin: $e"); } } // --- CRUD USERS --- Future createUser(Users user) async { final db = await database; final userMap = user.toMap(); userMap.remove('id'); // Remove ID for auto-increment final fields = userMap.keys.join(', '); final placeholders = List.filled(userMap.length, '?').join(', '); final result = await db.query( 'INSERT INTO users ($fields) VALUES ($placeholders)', userMap.values.toList() ); return result.insertId!; } Future updateUser(Users user) async { final db = await database; final userMap = user.toMap(); final id = userMap.remove('id'); final setClause = userMap.keys.map((key) => '$key = ?').join(', '); final values = [...userMap.values, id]; final result = await db.query( 'UPDATE users SET $setClause WHERE id = ?', values ); return result.affectedRows!; } Future deleteUser(int id) async { final db = await database; final result = await db.query('DELETE FROM users WHERE id = ?', [id]); return result.affectedRows!; } Future> getAllUsers() async { final db = await database; final result = await db.query(''' SELECT users.*, roles.designation as role_name FROM users INNER JOIN roles ON users.role_id = roles.id ORDER BY users.id ASC '''); return result.map((row) => Users.fromMap(row.fields)).toList(); } // --- CRUD ROLES --- Future createRole(Role role) async { final db = await database; final result = await db.query( 'INSERT INTO roles (designation) VALUES (?)', [role.designation] ); return result.insertId!; } Future> getRoles() async { final db = await database; final result = await db.query('SELECT * FROM roles ORDER BY designation ASC'); return result.map((row) => Role.fromMap(row.fields)).toList(); } Future updateRole(Role role) async { final db = await database; final result = await db.query( 'UPDATE roles SET designation = ? WHERE id = ?', [role.designation, role.id] ); return result.affectedRows!; } Future deleteRole(int? id) async { final db = await database; final result = await db.query('DELETE FROM roles WHERE id = ?', [id]); return result.affectedRows!; } // --- PERMISSIONS --- Future> getAllPermissions() async { final db = await database; final result = await db.query('SELECT * FROM permissions ORDER BY name ASC'); return result.map((row) => Permission.fromMap(row.fields)).toList(); } Future> getPermissionsForRole(int roleId) async { final db = await database; final result = await db.query(''' SELECT p.id, p.name FROM permissions p JOIN role_permissions rp ON p.id = rp.permission_id WHERE rp.role_id = ? ORDER BY p.name ASC ''', [roleId]); return result.map((row) => Permission.fromMap(row.fields)).toList(); } Future> getPermissionsForRoleAndMenu(int roleId, int menuId) async { final db = await database; final result = await db.query(''' SELECT p.id, p.name FROM permissions p JOIN role_menu_permissions rmp ON p.id = rmp.permission_id WHERE rmp.role_id = ? AND rmp.menu_id = ? ORDER BY p.name ASC ''', [roleId, menuId]); return result.map((row) => Permission.fromMap(row.fields)).toList(); } // --- AUTHENTIFICATION --- Future verifyUser(String username, String password) async { final db = await database; final result = await db.query(''' SELECT COUNT(*) as count FROM users WHERE username = ? AND password = ? ''', [username, password]); return (result.first['count'] as int) > 0; } Future getUser(String username) async { final db = await database; final result = await db.query(''' SELECT users.*, roles.designation as role_name FROM users INNER JOIN roles ON users.role_id = roles.id WHERE users.username = ? ''', [username]); if (result.isNotEmpty) { return Users.fromMap(result.first.fields); } else { throw Exception('User not found'); } } Future?> getUserCredentials(String username, String password) async { final db = await database; final result = await db.query(''' SELECT users.username, users.id, roles.designation as role_name, roles.id as role_id FROM users INNER JOIN roles ON users.role_id = roles.id WHERE username = ? AND password = ? ''', [username, password]); if (result.isNotEmpty) { final row = result.first; return { 'id': row['id'], 'username': row['username'] as String, 'role': row['role_name'] as String, 'role_id': row['role_id'], }; } else { return null; } } // --- CRUD PRODUCTS --- Future createProduct(Product product) async { final db = await database; // Si le produit a un point_de_vente_id, on l'utilise directement if (product.pointDeVenteId != null && product.pointDeVenteId! > 0) { final productMap = product.toMap(); productMap.remove('id'); final fields = productMap.keys.join(', '); final placeholders = List.filled(productMap.length, '?').join(', '); final result = await db.query( 'INSERT INTO products ($fields) VALUES ($placeholders)', productMap.values.toList() ); return result.insertId!; } // Sinon, on utilise le point de vente de l'utilisateur connecté final userCtrl = Get.find(); final currentPointDeVenteId = userCtrl.pointDeVenteId; final Map productData = product.toMap(); productData.remove('id'); if (currentPointDeVenteId > 0) { productData['point_de_vente_id'] = currentPointDeVenteId; } final fields = productData.keys.join(', '); final placeholders = List.filled(productData.length, '?').join(', '); final result = await db.query( 'INSERT INTO products ($fields) VALUES ($placeholders)', productData.values.toList() ); return result.insertId!; } Future> getProducts() async { final db = await database; final result = await db.query('SELECT * FROM products ORDER BY name ASC'); return result.map((row) => Product.fromMap(row.fields)).toList(); } Future updateProduct(Product product) async { final db = await database; final productMap = product.toMap(); final id = productMap.remove('id'); final setClause = productMap.keys.map((key) => '$key = ?').join(', '); final values = [...productMap.values, id]; final result = await db.query( 'UPDATE products SET $setClause WHERE id = ?', values ); return result.affectedRows!; } Future getProductById(int id) async { final db = await database; final result = await db.query('SELECT * FROM products WHERE id = ?', [id]); if (result.isNotEmpty) { return Product.fromMap(result.first.fields); } return null; } Future deleteProduct(int? id) async { final db = await database; final result = await db.query('DELETE FROM products WHERE id = ?', [id]); return result.affectedRows!; } // --- CRUD CLIENTS --- Future createClient(Client client) async { final db = await database; final clientMap = client.toMap(); clientMap.remove('id'); final fields = clientMap.keys.join(', '); final placeholders = List.filled(clientMap.length, '?').join(', '); final result = await db.query( 'INSERT INTO clients ($fields) VALUES ($placeholders)', clientMap.values.toList() ); return result.insertId!; } Future> getClients() async { final db = await database; final result = await db.query( 'SELECT * FROM clients WHERE actif = 1 ORDER BY nom ASC, prenom ASC' ); return result.map((row) => Client.fromMap(row.fields)).toList(); } Future getClientById(int id) async { final db = await database; final result = await db.query('SELECT * FROM clients WHERE id = ?', [id]); if (result.isNotEmpty) { return Client.fromMap(result.first.fields); } return null; } // --- POINTS DE VENTE --- Future>> getPointsDeVente() async { final db = await database; try { final result = await db.query( 'SELECT * FROM points_de_vente WHERE nom IS NOT NULL AND nom != "" ORDER BY nom ASC' ); if (result.isEmpty) { print("Aucun point de vente trouvé dans la base de données"); await insertDefaultPointsDeVente(); final newResult = await db.query('SELECT * FROM points_de_vente ORDER BY nom ASC'); return newResult.map((row) => row.fields).toList(); } return result.map((row) => row.fields).toList(); } catch (e) { print("Erreur lors de la récupération des points de vente: $e"); return []; } } // --- STATISTIQUES --- Future> getStatistiques() async { final db = await database; final totalClients = await db.query('SELECT COUNT(*) as count FROM clients WHERE actif = 1'); final totalCommandes = await db.query('SELECT COUNT(*) as count FROM commandes'); final totalProduits = await db.query('SELECT COUNT(*) as count FROM products'); final chiffreAffaires = await db.query('SELECT SUM(montantTotal) as total FROM commandes WHERE statut != 5'); return { 'totalClients': totalClients.first['count'], 'totalCommandes': totalCommandes.first['count'], 'totalProduits': totalProduits.first['count'], 'chiffreAffaires': chiffreAffaires.first['total'] ?? 0.0, }; } // --- MÉTHODES UTILITAIRES --- Future _addMissingMenus(MySqlConnection db) async { final menusToAdd = [ {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'}, {'name': 'Gérer les commandes', 'route': '/gerer-commandes'}, {'name': 'Points de vente', 'route': '/points-de-vente'}, ]; for (var menu in menusToAdd) { final existing = await db.query( 'SELECT COUNT(*) as count FROM menu WHERE route = ?', [menu['route']] ); final count = existing.first['count'] as int; if (count == 0) { await db.query( 'INSERT INTO menu (name, route) VALUES (?, ?)', [menu['name'], menu['route']] ); print("Menu ajouté: ${menu['name']}"); } } } Future _updateExistingRolePermissions(MySqlConnection db) async { final superAdminRole = await db.query('SELECT id FROM roles WHERE designation = ?', ['Super Admin']); if (superAdminRole.isNotEmpty) { final superAdminRoleId = superAdminRole.first['id']; final permissions = await db.query('SELECT * FROM permissions'); final menus = await db.query('SELECT * FROM menu'); // Vérifier et ajouter les permissions manquantes pour le Super Admin sur tous les menus for (var menu in menus) { for (var permission in permissions) { final existingPermission = await db.query(''' SELECT COUNT(*) as count FROM role_menu_permissions WHERE role_id = ? AND menu_id = ? AND permission_id = ? ''', [superAdminRoleId, menu['id'], permission['id']]); final count = existingPermission.first['count'] as int; if (count == 0) { await db.query(''' INSERT IGNORE INTO role_menu_permissions (role_id, menu_id, permission_id) VALUES (?, ?, ?) ''', [superAdminRoleId, menu['id'], permission['id']]); } } } // Assigner les permissions de base aux autres rôles pour les nouveaux menus final adminRole = await db.query('SELECT id FROM roles WHERE designation = ?', ['Admin']); final userRole = await db.query('SELECT id FROM roles WHERE designation = ?', ['User']); if (adminRole.isNotEmpty && userRole.isNotEmpty) { await _assignBasicPermissionsToRoles(db, adminRole.first['id'], userRole.first['id']); } print("Permissions mises à jour pour tous les rôles"); } } Future _assignBasicPermissionsToRoles(MySqlConnection db, int adminRoleId, int userRoleId) async { // Implémentation similaire mais adaptée pour MySQL print("Permissions de base assignées aux rôles Admin et User"); } // --- FERMETURE --- Future close() async { if (_connection != null) { try { await _connection!.close(); _connection = null; print("Connexion MySQL fermée"); } catch (e) { print("Erreur lors de la fermeture de la connexion: $e"); _connection = null; } } } // Pour le débogage - supprimer toutes les tables (équivalent à supprimer la DB) Future deleteDatabaseFile() async { final db = await database; try { // Désactiver les contraintes de clés étrangères temporairement await db.query('SET FOREIGN_KEY_CHECKS = 0'); // Lister toutes les tables final tables = await db.query('SHOW TABLES'); // Supprimer toutes les tables for (var table in tables) { final tableName = table.values?.first; await db.query('DROP TABLE IF EXISTS `$tableName`'); } // Réactiver les contraintes de clés étrangères await db.query('SET FOREIGN_KEY_CHECKS = 1'); print("Toutes les tables ont été supprimées"); } catch (e) { print("Erreur lors de la suppression des tables: $e"); } } Future printDatabaseInfo() async { final db = await database; print("=== INFORMATIONS DE LA BASE DE DONNÉES MYSQL ==="); try { final userCountResult = await db.query('SELECT COUNT(*) as count FROM users'); final userCount = userCountResult.first['count'] as int; print("Nombre d'utilisateurs: $userCount"); final users = await getAllUsers(); print("Utilisateurs:"); for (var user in users) { print(" - ${user.username} (${user.name}) - Email: ${user.email}"); } final roles = await getRoles(); print("Rôles:"); for (var role in roles) { print(" - ${role.designation} (ID: ${role.id})"); } final permissions = await getAllPermissions(); print("Permissions:"); for (var permission in permissions) { print(" - ${permission.name} (ID: ${permission.id})"); } print("========================================="); } catch (e) { print("Erreur lors de l'affichage des informations: $e"); } } // --- MÉTHODES SUPPLÉMENTAIRES POUR COMMANDES --- Future createCommande(Commande commande) async { final db = await database; final commandeMap = commande.toMap(); commandeMap.remove('id'); final fields = commandeMap.keys.join(', '); final placeholders = List.filled(commandeMap.length, '?').join(', '); final result = await db.query( 'INSERT INTO commandes ($fields) VALUES ($placeholders)', commandeMap.values.toList() ); return result.insertId!; } Future> getCommandes() async { final db = await database; final result = await db.query(''' SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail FROM commandes c LEFT JOIN clients cl ON c.clientId = cl.id ORDER BY c.dateCommande DESC '''); return result.map((row) => Commande.fromMap(row.fields)).toList(); } Future getCommandeById(int id) async { final db = await database; final result = await db.query(''' SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail FROM commandes c LEFT JOIN clients cl ON c.clientId = cl.id WHERE c.id = ? ''', [id]); if (result.isNotEmpty) { return Commande.fromMap(result.first.fields); } return null; } Future updateCommande(Commande commande) async { final db = await database; final commandeMap = commande.toMap(); final id = commandeMap.remove('id'); final setClause = commandeMap.keys.map((key) => '$key = ?').join(', '); final values = [...commandeMap.values, id]; final result = await db.query( 'UPDATE commandes SET $setClause WHERE id = ?', values ); return result.affectedRows!; } Future deleteCommande(int id) async { final db = await database; final result = await db.query('DELETE FROM commandes WHERE id = ?', [id]); return result.affectedRows!; } // --- DÉTAILS COMMANDES --- Future createDetailCommande(DetailCommande detail) async { final db = await database; final detailMap = detail.toMap(); detailMap.remove('id'); final fields = detailMap.keys.join(', '); final placeholders = List.filled(detailMap.length, '?').join(', '); final result = await db.query( 'INSERT INTO details_commandes ($fields) VALUES ($placeholders)', detailMap.values.toList() ); return result.insertId!; } Future> getDetailsCommande(int commandeId) async { final db = await database; final result = await db.query(''' SELECT dc.*, p.name as produitNom, p.image as produitImage, p.reference as produitReference FROM details_commandes dc LEFT JOIN products p ON dc.produitId = p.id WHERE dc.commandeId = ? ORDER BY dc.id ''', [commandeId]); return result.map((row) => DetailCommande.fromMap(row.fields)).toList(); } // --- RECHERCHE PRODUITS --- Future getProductByReference(String reference) async { final db = await database; final result = await db.query( 'SELECT * FROM products WHERE reference = ?', [reference] ); if (result.isNotEmpty) { return Product.fromMap(result.first.fields); } return null; } Future getProductByIMEI(String imei) async { final db = await database; final result = await db.query( 'SELECT * FROM products WHERE imei = ?', [imei] ); if (result.isNotEmpty) { return Product.fromMap(result.first.fields); } return null; } Future> getCategories() async { final db = await database; final result = await db.query('SELECT DISTINCT category FROM products ORDER BY category'); return result.map((row) => row['category'] as String).toList(); } Future> getProductsByCategory(String category) async { final db = await database; final result = await db.query( 'SELECT * FROM products WHERE category = ? ORDER BY name ASC', [category] ); return result.map((row) => Product.fromMap(row.fields)).toList(); } // --- RECHERCHE CLIENTS --- Future updateClient(Client client) async { final db = await database; final clientMap = client.toMap(); final id = clientMap.remove('id'); final setClause = clientMap.keys.map((key) => '$key = ?').join(', '); final values = [...clientMap.values, id]; final result = await db.query( 'UPDATE clients SET $setClause WHERE id = ?', values ); return result.affectedRows!; } Future deleteClient(int id) async { final db = await database; // Soft delete final result = await db.query( 'UPDATE clients SET actif = 0 WHERE id = ?', [id] ); return result.affectedRows!; } Future> searchClients(String query) async { final db = await database; final result = await db.query(''' SELECT * FROM clients WHERE actif = 1 AND (nom LIKE ? OR prenom LIKE ? OR email LIKE ?) ORDER BY nom ASC, prenom ASC ''', ['%$query%', '%$query%', '%$query%']); return result.map((row) => Client.fromMap(row.fields)).toList(); } Future getClientByEmail(String email) async { final db = await database; final result = await db.query( 'SELECT * FROM clients WHERE email = ? AND actif = 1 LIMIT 1', [email.trim().toLowerCase()] ); if (result.isNotEmpty) { return Client.fromMap(result.first.fields); } return null; } Future findExistingClient({ String? email, String? telephone, String? nom, String? prenom, }) async { // Priorité 1: Recherche par email if (email != null && email.isNotEmpty) { final clientByEmail = await getClientByEmail(email); if (clientByEmail != null) { return clientByEmail; } } // Priorité 2: Recherche par téléphone if (telephone != null && telephone.isNotEmpty) { final db = await database; final result = await db.query( 'SELECT * FROM clients WHERE telephone = ? AND actif = 1 LIMIT 1', [telephone.trim()] ); if (result.isNotEmpty) { return Client.fromMap(result.first.fields); } } // Priorité 3: Recherche par nom et prénom if (nom != null && nom.isNotEmpty && prenom != null && prenom.isNotEmpty) { final db = await database; final result = await db.query( 'SELECT * FROM clients WHERE LOWER(nom) = ? AND LOWER(prenom) = ? AND actif = 1 LIMIT 1', [nom.trim().toLowerCase(), prenom.trim().toLowerCase()] ); if (result.isNotEmpty) { return Client.fromMap(result.first.fields); } } return null; } // --- UTILISATEURS SPÉCIALISÉS --- Future> getCommercialUsers() async { final db = await database; final result = await db.query(''' SELECT users.*, roles.designation as role_name FROM users INNER JOIN roles ON users.role_id = roles.id WHERE roles.designation = 'commercial' ORDER BY users.id ASC '''); return result.map((row) => Users.fromMap(row.fields)).toList(); } Future getUserById(int id) async { final db = await database; final result = await db.query(''' SELECT users.*, roles.designation as role_name FROM users INNER JOIN roles ON users.role_id = roles.id WHERE users.id = ? ''', [id]); if (result.isNotEmpty) { return Users.fromMap(result.first.fields); } return null; } Future getUserCount() async { final db = await database; final result = await db.query('SELECT COUNT(*) as count FROM users'); return result.first['count'] as int; } // --- PERMISSIONS AVANCÉES --- Future assignRoleMenuPermission(int roleId, int menuId, int permissionId) async { final db = await database; await db.query(''' INSERT IGNORE INTO role_menu_permissions (role_id, menu_id, permission_id) VALUES (?, ?, ?) ''', [roleId, menuId, permissionId]); } Future removeRoleMenuPermission(int roleId, int menuId, int permissionId) async { final db = await database; await db.query(''' DELETE FROM role_menu_permissions WHERE role_id = ? AND menu_id = ? AND permission_id = ? ''', [roleId, menuId, permissionId]); } Future isSuperAdmin(String username) async { final db = await database; final result = await db.query(''' SELECT COUNT(*) as count FROM users u INNER JOIN roles r ON u.role_id = r.id WHERE u.username = ? AND r.designation = 'Super Admin' ''', [username]); return (result.first['count'] as int) > 0; } Future hasPermission(String username, String permissionName, String menuRoute) async { final db = await database; final result = await db.query(''' SELECT COUNT(*) as count FROM permissions p JOIN role_menu_permissions rmp ON p.id = rmp.permission_id JOIN roles r ON rmp.role_id = r.id JOIN users u ON u.role_id = r.id JOIN menu m ON m.route = ? WHERE u.username = ? AND p.name = ? AND rmp.menu_id = m.id ''', [menuRoute, username, permissionName]); return (result.first['count'] as int) > 0; } // --- GESTION STOCK --- Future updateStock(int productId, int newStock) async { final db = await database; final result = await db.query( 'UPDATE products SET stock = ? WHERE id = ?', [newStock, productId] ); return result.affectedRows!; } Future> getLowStockProducts({int threshold = 5}) async { final db = await database; final result = await db.query( 'SELECT * FROM products WHERE stock <= ? AND stock > 0 ORDER BY stock ASC', [threshold] ); return result.map((row) => Product.fromMap(row.fields)).toList(); } // --- POINTS DE VENTE AVANCÉS --- Future createPointDeVente(String designation, String code) async { final db = await database; final result = await db.query( 'INSERT IGNORE INTO points_de_vente (nom) VALUES (?)', [designation] ); return result.insertId ?? 0; } Future updatePointDeVente(int id, String newDesignation, String newCode) async { final db = await database; final result = await db.query( 'UPDATE points_de_vente SET nom = ? WHERE id = ?', [newDesignation, id] ); return result.affectedRows!; } Future deletePointDeVente(int id) async { final db = await database; final result = await db.query('DELETE FROM points_de_vente WHERE id = ?', [id]); return result.affectedRows!; } Future?> getPointDeVenteById(int id) async { final db = await database; final result = await db.query('SELECT * FROM points_de_vente WHERE id = ?', [id]); return result.isNotEmpty ? result.first.fields : null; } Future getOrCreatePointDeVenteByNom(String nom) async { final db = await database; // Vérifier si le point de vente existe déjà final existing = await db.query( 'SELECT id FROM points_de_vente WHERE nom = ?', [nom.trim()] ); if (existing.isNotEmpty) { return existing.first['id'] as int; } // Créer le point de vente s'il n'existe pas try { final result = await db.query( 'INSERT INTO points_de_vente (nom) VALUES (?)', [nom.trim()] ); print("Point de vente créé: $nom (ID: ${result.insertId})"); return result.insertId; } catch (e) { print("Erreur lors de la création du point de vente $nom: $e"); return null; } } Future getPointDeVenteNomById(int id) async { if (id == 0) return null; final db = await database; try { final result = await db.query( 'SELECT nom FROM points_de_vente WHERE id = ? LIMIT 1', [id] ); return result.isNotEmpty ? result.first['nom'] as String : null; } catch (e) { print("Erreur getPointDeVenteNomById: $e"); return null; } } // --- RECHERCHE AVANCÉE --- Future> searchProducts({ String? name, String? imei, String? reference, bool onlyInStock = false, String? category, int? pointDeVenteId, }) async { final db = await database; List whereConditions = []; List whereArgs = []; if (name != null && name.isNotEmpty) { whereConditions.add('name LIKE ?'); whereArgs.add('%$name%'); } if (imei != null && imei.isNotEmpty) { whereConditions.add('imei LIKE ?'); whereArgs.add('%$imei%'); } if (reference != null && reference.isNotEmpty) { whereConditions.add('reference LIKE ?'); whereArgs.add('%$reference%'); } if (onlyInStock) { whereConditions.add('stock > 0'); } if (category != null && category.isNotEmpty) { whereConditions.add('category = ?'); whereArgs.add(category); } if (pointDeVenteId != null && pointDeVenteId > 0) { whereConditions.add('point_de_vente_id = ?'); whereArgs.add(pointDeVenteId); } String whereClause = whereConditions.isNotEmpty ? 'WHERE ${whereConditions.join(' AND ')}' : ''; final result = await db.query( 'SELECT * FROM products $whereClause ORDER BY name ASC', whereArgs ); return result.map((row) => Product.fromMap(row.fields)).toList(); } Future findProductByCode(String code) async { final db = await database; // Essayer de trouver par référence d'abord var result = await db.query( 'SELECT * FROM products WHERE reference = ? LIMIT 1', [code] ); if (result.isNotEmpty) { return Product.fromMap(result.first.fields); } // Ensuite par IMEI result = await db.query( 'SELECT * FROM products WHERE imei = ? LIMIT 1', [code] ); if (result.isNotEmpty) { return Product.fromMap(result.first.fields); } // Enfin par QR code si disponible result = await db.query( 'SELECT * FROM products WHERE qrCode = ? LIMIT 1', [code] ); if (result.isNotEmpty) { return Product.fromMap(result.first.fields); } return null; } // --- TRANSACTIONS COMPLEXES --- Future createCommandeComplete(Client client, Commande commande, List details) async { final db = await database; try { await db.query('START TRANSACTION'); // 1. Utiliser createOrGetClient au lieu de créer directement final existingOrNewClient = await createOrGetClient(client); final clientId = existingOrNewClient.id!; // 2. Créer la commande avec le bon clientId final commandeMap = commande.toMap(); commandeMap.remove('id'); commandeMap['clientId'] = clientId; final commandeFields = commandeMap.keys.join(', '); final commandePlaceholders = List.filled(commandeMap.length, '?').join(', '); final commandeResult = await db.query( 'INSERT INTO commandes ($commandeFields) VALUES ($commandePlaceholders)', commandeMap.values.toList() ); final commandeId = commandeResult.insertId!; // 3. Créer les détails de commande for (final detail in details) { final detailMap = detail.toMap(); detailMap.remove('id'); detailMap['commandeId'] = commandeId; final detailFields = detailMap.keys.join(', '); final detailPlaceholders = List.filled(detailMap.length, '?').join(', '); await db.query( 'INSERT INTO details_commandes ($detailFields) VALUES ($detailPlaceholders)', detailMap.values.toList() ); // 4. Mettre à jour le stock await db.query( 'UPDATE products SET stock = stock - ? WHERE id = ?', [detail.quantite, detail.produitId] ); } await db.query('COMMIT'); return commandeId; } catch (e) { await db.query('ROLLBACK'); print("Erreur lors de la création de la commande complète: $e"); rethrow; } } // --- STATISTIQUES AVANCÉES --- Future> getProductCountByCategory() async { final db = await database; final result = await db.query(''' SELECT category, COUNT(*) as count FROM products GROUP BY category ORDER BY count DESC '''); return Map.fromEntries(result.map((row) => MapEntry(row['category'] as String, row['count'] as int))); } Future>> getStockStatsByCategory() async { final db = await database; final result = await db.query(''' SELECT category, COUNT(*) as total_products, SUM(CASE WHEN stock > 0 THEN 1 ELSE 0 END) as in_stock, SUM(CASE WHEN stock = 0 OR stock IS NULL THEN 1 ELSE 0 END) as out_of_stock, SUM(stock) as total_stock FROM products GROUP BY category ORDER BY category '''); Map> stats = {}; for (var row in result) { stats[row['category'] as String] = { 'total': row['total_products'] as int, 'in_stock': row['in_stock'] as int, 'out_of_stock': row['out_of_stock'] as int, 'total_stock': (row['total_stock'] as int?) ?? 0, }; } return stats; } Future>> getMostSoldProducts({int limit = 10}) async { final db = await database; final result = await db.query(''' SELECT p.id, p.name, p.price, p.stock, p.category, SUM(dc.quantite) as total_sold, COUNT(DISTINCT dc.commandeId) as order_count FROM products p INNER JOIN details_commandes dc ON p.id = dc.produitId INNER JOIN commandes c ON dc.commandeId = c.id WHERE c.statut != 5 -- Exclure les commandes annulées GROUP BY p.id, p.name, p.price, p.stock, p.category ORDER BY total_sold DESC LIMIT ? ''', [limit]); return result.map((row) => row.fields).toList(); } // --- DÉBOGAGE --- Future debugPointsDeVenteTable() async { final db = await database; try { // Compte le nombre d'entrées final count = await db.query("SELECT COUNT(*) as count FROM points_de_vente"); print("Nombre de points de vente: ${count.first['count']}"); // Affiche le contenu final content = await db.query('SELECT * FROM points_de_vente'); print("Contenu de la table points_de_vente:"); for (var row in content) { print("ID: ${row['id']}, Nom: ${row['nom']}"); } } catch (e) { print("Erreur debug table points_de_vente: $e"); } } // 1. Méthodes pour les clients Future getClientByTelephone(String telephone) async { final db = await database; final result = await db.query( 'SELECT * FROM clients WHERE telephone = ? AND actif = 1 LIMIT 1', [telephone.trim()] ); if (result.isNotEmpty) { return Client.fromMap(result.first.fields); } return null; } Future getClientByNomPrenom(String nom, String prenom) async { final db = await database; final result = await db.query( 'SELECT * FROM clients WHERE LOWER(nom) = ? AND LOWER(prenom) = ? AND actif = 1 LIMIT 1', [nom.trim().toLowerCase(), prenom.trim().toLowerCase()] ); if (result.isNotEmpty) { return Client.fromMap(result.first.fields); } return null; } Future> suggestClients(String query) async { if (query.trim().isEmpty) return []; final db = await database; final searchQuery = '%${query.trim().toLowerCase()}%'; final result = await db.query(''' SELECT * FROM clients WHERE actif = 1 AND ( LOWER(nom) LIKE ? OR LOWER(prenom) LIKE ? OR LOWER(email) LIKE ? OR telephone LIKE ? ) ORDER BY nom ASC, prenom ASC LIMIT 10 ''', [searchQuery, searchQuery, searchQuery, searchQuery]); return result.map((row) => Client.fromMap(row.fields)).toList(); } Future> checkPotentialDuplicates({ required String nom, required String prenom, required String email, required String telephone, }) async { final db = await database; final result = await db.query(''' SELECT * FROM clients WHERE actif = 1 AND ( (LOWER(nom) = ? AND LOWER(prenom) = ?) OR email = ? OR telephone = ? ) ORDER BY nom ASC, prenom ASC ''', [ nom.trim().toLowerCase(), prenom.trim().toLowerCase(), email.trim().toLowerCase(), telephone.trim() ]); return result.map((row) => Client.fromMap(row.fields)).toList(); } Future createOrGetClient(Client newClient) async { final existingClient = await findExistingClient( email: newClient.email, telephone: newClient.telephone, nom: newClient.nom, prenom: newClient.prenom, ); if (existingClient != null) { return existingClient; } final clientId = await createClient(newClient); final createdClient = await getClientById(clientId); if (createdClient != null) { return createdClient; } else { throw Exception("Erreur lors de la création du client"); } } // 2. Méthodes pour les produits Future> getSimilarProducts(Product product, {int limit = 5}) async { final db = await database; final result = await db.query(''' SELECT * FROM products WHERE id != ? AND ( category = ? OR name LIKE ? ) ORDER BY CASE WHEN category = ? THEN 1 ELSE 2 END, name ASC LIMIT ? ''', [ product.id, product.category, '%${product.name.split(' ').first}%', product.category, limit ]); return result.map((row) => Product.fromMap(row.fields)).toList(); } // 3. Méthodes pour les commandes Future updateStatutCommande(int commandeId, StatutCommande statut) async { final db = await database; final result = await db.query( 'UPDATE commandes SET statut = ? WHERE id = ?', [statut.index, commandeId] ); return result.affectedRows!; } Future> getCommandesByClient(int clientId) async { final db = await database; final result = await db.query(''' SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail FROM commandes c LEFT JOIN clients cl ON c.clientId = cl.id WHERE c.clientId = ? ORDER BY c.dateCommande DESC ''', [clientId]); return result.map((row) => Commande.fromMap(row.fields)).toList(); } Future> getCommandesByStatut(StatutCommande statut) async { final db = await database; final result = await db.query(''' SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail FROM commandes c LEFT JOIN clients cl ON c.clientId = cl.id WHERE c.statut = ? ORDER BY c.dateCommande DESC ''', [statut.index]); return result.map((row) => Commande.fromMap(row.fields)).toList(); } Future updateValidateurCommande(int commandeId, int validateurId) async { final db = await database; final result = await db.query(''' UPDATE commandes SET validateurId = ?, statut = ? WHERE id = ? ''', [validateurId, StatutCommande.confirmee.index, commandeId]); return result.affectedRows!; } // --- CRUD MENUS --- // Ajoutez ces méthodes dans votre classe AppDatabase Future>> getAllMenus() async { final db = await database; try { final result = await db.query('SELECT * FROM menu ORDER BY name ASC'); return result.map((row) => row.fields).toList(); } catch (e) { print("Erreur lors de la récupération des menus: $e"); return []; } } Future?> getMenuById(int id) async { final db = await database; try { final result = await db.query('SELECT * FROM menu WHERE id = ? LIMIT 1', [id]); return result.isNotEmpty ? result.first.fields : null; } catch (e) { print("Erreur getMenuById: $e"); return null; } } Future?> getMenuByRoute(String route) async { final db = await database; try { final result = await db.query('SELECT * FROM menu WHERE route = ? LIMIT 1', [route]); return result.isNotEmpty ? result.first.fields : null; } catch (e) { print("Erreur getMenuByRoute: $e"); return null; } } Future createMenu(String name, String route) async { final db = await database; try { // Vérifier si le menu existe déjà final existing = await db.query('SELECT COUNT(*) as count FROM menu WHERE route = ?', [route]); final count = existing.first['count'] as int; if (count > 0) { throw Exception('Un menu avec cette route existe déjà'); } final result = await db.query( 'INSERT INTO menu (name, route) VALUES (?, ?)', [name, route] ); return result.insertId!; } catch (e) { print("Erreur createMenu: $e"); rethrow; } } Future updateMenu(int id, String name, String route) async { final db = await database; try { final result = await db.query( 'UPDATE menu SET name = ?, route = ? WHERE id = ?', [name, route, id] ); return result.affectedRows!; } catch (e) { print("Erreur updateMenu: $e"); rethrow; } } Future deleteMenu(int id) async { final db = await database; try { // D'abord supprimer les permissions associées await db.query('DELETE FROM role_menu_permissions WHERE menu_id = ?', [id]); // Ensuite supprimer le menu final result = await db.query('DELETE FROM menu WHERE id = ?', [id]); return result.affectedRows!; } catch (e) { print("Erreur deleteMenu: $e"); rethrow; } } Future>> getMenusForRole(int roleId) async { final db = await database; try { final result = await db.query(''' SELECT DISTINCT m.* FROM menu m INNER JOIN role_menu_permissions rmp ON m.id = rmp.menu_id WHERE rmp.role_id = ? ORDER BY m.name ASC ''', [roleId]); return result.map((row) => row.fields).toList(); } catch (e) { print("Erreur getMenusForRole: $e"); return []; } } Future hasMenuAccess(int roleId, String menuRoute) async { final db = await database; try { final result = await db.query(''' SELECT COUNT(*) as count FROM role_menu_permissions rmp INNER JOIN menu m ON rmp.menu_id = m.id WHERE rmp.role_id = ? AND m.route = ? ''', [roleId, menuRoute]); return (result.first['count'] as int) > 0; } catch (e) { print("Erreur hasMenuAccess: $e"); return false; } } Future findClientByAnyIdentifier({ String? email, String? telephone, String? nom, String? prenom, }) async { final db = await database; // Recherche par email si fourni if (email != null && email.isNotEmpty) { final client = await getClientByEmail(email); if (client != null) return client; } // Recherche par téléphone si fourni if (telephone != null && telephone.isNotEmpty) { final client = await getClientByTelephone(telephone); if (client != null) return client; } // Recherche par nom et prénom si fournis if (nom != null && nom.isNotEmpty && prenom != null && prenom.isNotEmpty) { final client = await getClientByNomPrenom(nom, prenom); if (client != null) return client; } return null; } Future migrateDatabaseForDiscountAndGift() async { final db = await database; try { // Ajouter les colonnes de remise à la table commandes await db.query(''' ALTER TABLE commandes ADD COLUMN remisePourcentage DECIMAL(5,2) NULL '''); await db.query(''' ALTER TABLE commandes ADD COLUMN remiseMontant DECIMAL(10,2) NULL '''); await db.query(''' ALTER TABLE commandes ADD COLUMN montantApresRemise DECIMAL(10,2) NULL '''); // Ajouter la colonne cadeau à la table details_commandes await db.query(''' ALTER TABLE details_commandes ADD COLUMN estCadeau TINYINT(1) DEFAULT 0 '''); print("Migration pour remise et cadeau terminée avec succès"); } catch (e) { // Les colonnes existent probablement déjà print("Migration déjà effectuée ou erreur: $e"); } } Future> getDetailsCommandeAvecCadeaux(int commandeId) async { final db = await database; final result = await db.query(''' SELECT dc.*, p.name as produitNom, p.image as produitImage, p.reference as produitReference FROM details_commandes dc LEFT JOIN products p ON dc.produitId = p.id WHERE dc.commandeId = ? ORDER BY dc.estCadeau ASC, dc.id ''', [commandeId]); return result.map((row) => DetailCommande.fromMap(row.fields)).toList(); } Future updateCommandeAvecRemise(int commandeId, { double? remisePourcentage, double? remiseMontant, double? montantApresRemise, }) async { final db = await database; List setClauses = []; List values = []; if (remisePourcentage != null) { setClauses.add('remisePourcentage = ?'); values.add(remisePourcentage); } if (remiseMontant != null) { setClauses.add('remiseMontant = ?'); values.add(remiseMontant); } if (montantApresRemise != null) { setClauses.add('montantApresRemise = ?'); values.add(montantApresRemise); } if (setClauses.isEmpty) return 0; values.add(commandeId); final result = await db.query( 'UPDATE commandes SET ${setClauses.join(', ')} WHERE id = ?', values ); return result.affectedRows!; } Future createDetailCommandeCadeau(DetailCommande detail) async { final db = await database; final detailMap = detail.toMap(); detailMap.remove('id'); detailMap['estCadeau'] = 1; // Marquer comme cadeau detailMap['prixUnitaire'] = 0.0; // Prix zéro pour les cadeaux detailMap['sousTotal'] = 0.0; // Sous-total zéro pour les cadeaux final fields = detailMap.keys.join(', '); final placeholders = List.filled(detailMap.length, '?').join(', '); final result = await db.query( 'INSERT INTO details_commandes ($fields) VALUES ($placeholders)', detailMap.values.toList() ); return result.insertId!; } Future> getCadeauxCommande(int commandeId) async { final db = await database; final result = await db.query(''' SELECT dc.*, p.name as produitNom, p.image as produitImage, p.reference as produitReference FROM details_commandes dc LEFT JOIN products p ON dc.produitId = p.id WHERE dc.commandeId = ? AND dc.estCadeau = 1 ORDER BY dc.id ''', [commandeId]); return result.map((row) => DetailCommande.fromMap(row.fields)).toList(); } Future calculateMontantTotalSansCadeaux(int commandeId) async { final db = await database; final result = await db.query(''' SELECT SUM(sousTotal) as total FROM details_commandes WHERE commandeId = ? AND (estCadeau = 0 OR estCadeau IS NULL) ''', [commandeId]); final total = result.first['total']; return total != null ? (total as num).toDouble() : 0.0; } Future supprimerRemiseCommande(int commandeId) async { final db = await database; final result = await db.query(''' UPDATE commandes SET remisePourcentage = NULL, remiseMontant = NULL, montantApresRemise = NULL WHERE id = ? ''', [commandeId]); return result.affectedRows!; } }