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 { // Vérifier et ajouter uniquement 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 { await _addMissingMenus(db); // Seulement ajouter les menus manquants } 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 _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!; } }