You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3272 lines
103 KiB
3272 lines
103 KiB
import 'dart:async';
|
|
import 'dart:math' as console;
|
|
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';
|
|
import 'package:intl/intl.dart';
|
|
|
|
class AppDatabase {
|
|
static final AppDatabase instance = AppDatabase._init();
|
|
MySqlConnection? _connection;
|
|
|
|
AppDatabase._init();
|
|
|
|
Future<MySqlConnection> 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<void> initDatabase() async {
|
|
_connection = await _initDB();
|
|
// await _createDB();
|
|
|
|
await insertDefaultPermissions();
|
|
await insertDefaultMenus();
|
|
await insertDefaultRoles();
|
|
await insertDefaultSuperAdmin();
|
|
await insertDefaultPointsDeVente();
|
|
}
|
|
|
|
Future<MySqlConnection> _initDB() async {
|
|
try {
|
|
final config = await DatabaseConfig.getSmartConfig();
|
|
// console.log(config as num);
|
|
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;
|
|
}
|
|
throw Exception("La connexion MySQL n'a pas pu être établie.");
|
|
}
|
|
|
|
// --- MÉTHODES D'INSERTION PAR DÉFAUT ---
|
|
|
|
//
|
|
Future<void> 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<void> insertDefaultMenus() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await _addMissingMenus(db); // Seulement ajouter les menus manquants
|
|
} catch (e) {
|
|
print("Erreur insertDefaultMenus: $e");
|
|
}
|
|
}
|
|
|
|
Future<void> 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<String, int> 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<void> 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<void> 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<int> 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!;
|
|
}
|
|
|
|
// ✅ CORRIGÉ: Méthode updateUser simplifiée et corrigée
|
|
Future<int> updateUser(Users user) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
print("🔄 Mise à jour utilisateur ID: ${user.id}");
|
|
print("Données: ${user.toMap()}");
|
|
|
|
final result = await db.query('''
|
|
UPDATE users
|
|
SET
|
|
name = ?,
|
|
lastname = ?,
|
|
email = ?,
|
|
username = ?,
|
|
password = ?,
|
|
role_id = ?,
|
|
point_de_vente_id = ?
|
|
WHERE id = ?
|
|
''', [
|
|
user.name,
|
|
user.lastName,
|
|
user.email,
|
|
user.username,
|
|
user.password,
|
|
user.roleId,
|
|
user.pointDeVenteId,
|
|
user.id
|
|
]);
|
|
|
|
print(
|
|
"✅ Utilisateur mis à jour. Lignes affectées: ${result.affectedRows}");
|
|
return result.affectedRows!;
|
|
} catch (e) {
|
|
print("❌ Erreur lors de la mise à jour de l'utilisateur: $e");
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// ✅ AJOUTÉ: Méthode pour vérifier si l'utilisateur existe
|
|
Future<bool> userExists(int userId) async {
|
|
final db = await database;
|
|
try {
|
|
final result = await db
|
|
.query('SELECT COUNT(*) as count FROM users WHERE id = ?', [userId]);
|
|
return (result.first['count'] as int) > 0;
|
|
} catch (e) {
|
|
print("❌ Erreur vérification existence utilisateur: $e");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ✅ AJOUTÉ: Méthode pour vérifier les contraintes avant mise à jour
|
|
Future<String?> validateUserUpdate(Users user) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Vérifier si l'email existe déjà pour un autre utilisateur
|
|
final emailCheck = await db.query(
|
|
'SELECT COUNT(*) as count FROM users WHERE email = ? AND id != ?',
|
|
[user.email, user.id]);
|
|
if ((emailCheck.first['count'] as int) > 0) {
|
|
return 'Cet email est déjà utilisé par un autre utilisateur';
|
|
}
|
|
|
|
// Vérifier si le username existe déjà pour un autre utilisateur
|
|
final usernameCheck = await db.query(
|
|
'SELECT COUNT(*) as count FROM users WHERE username = ? AND id != ?',
|
|
[user.username, user.id]);
|
|
if ((usernameCheck.first['count'] as int) > 0) {
|
|
return 'Ce nom d\'utilisateur est déjà utilisé';
|
|
}
|
|
|
|
// Vérifier si le rôle existe
|
|
final roleCheck = await db.query(
|
|
'SELECT COUNT(*) as count FROM roles WHERE id = ?', [user.roleId]);
|
|
if ((roleCheck.first['count'] as int) == 0) {
|
|
return 'Le rôle sélectionné n\'existe pas';
|
|
}
|
|
|
|
// Vérifier si le point de vente existe (si spécifié)
|
|
if (user.pointDeVenteId != null && user.pointDeVenteId! > 0) {
|
|
final pointDeVenteCheck = await db.query(
|
|
'SELECT COUNT(*) as count FROM points_de_vente WHERE id = ?',
|
|
[user.pointDeVenteId]);
|
|
if ((pointDeVenteCheck.first['count'] as int) == 0) {
|
|
return 'Le point de vente sélectionné n\'existe pas';
|
|
}
|
|
}
|
|
|
|
return null; // Aucune erreur
|
|
} catch (e) {
|
|
return 'Erreur lors de la validation: $e';
|
|
}
|
|
}
|
|
|
|
Future<int> deleteUser(int id) async {
|
|
final db = await database;
|
|
final result = await db.query('DELETE FROM users WHERE id = ?', [id]);
|
|
return result.affectedRows!;
|
|
}
|
|
|
|
Future<List<Users>> 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<int> createRole(Role role) async {
|
|
final db = await database;
|
|
final result = await db.query(
|
|
'INSERT INTO roles (designation) VALUES (?)', [role.designation]);
|
|
return result.insertId!;
|
|
}
|
|
|
|
Future<List<Role>> 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<int> 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<int> deleteRole(int? id) async {
|
|
final db = await database;
|
|
final result = await db.query('DELETE FROM roles WHERE id = ?', [id]);
|
|
return result.affectedRows!;
|
|
}
|
|
|
|
// --- PERMISSIONS ---
|
|
|
|
Future<List<Permission>> 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<List<Permission>> 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<List<Permission>> 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<bool> 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<Users> 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<Map<String, dynamic>?> 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<int> 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<UserController>();
|
|
final currentPointDeVenteId = userCtrl.pointDeVenteId;
|
|
|
|
final Map<String, dynamic> 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<List<Product>> 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<int> 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<Product?> 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<int> 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<int> 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<List<Client>> 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<Client?> 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<List<Map<String, dynamic>>> 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 [];
|
|
}
|
|
}
|
|
|
|
Future<double> getValeurTotaleStock() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Récupérer tous les produits
|
|
final List<Map<String, dynamic>> products = (await db.query('products')) as List<Map<String, dynamic>>;
|
|
|
|
double valeurTotale = 0.0;
|
|
for (final product in products) {
|
|
final stock = (product['stock'] as num?)?.toDouble();
|
|
final price = (product['price'] as num?)?.toDouble();
|
|
|
|
// Vérifier que stock et price ne sont pas null
|
|
if (stock != null && price != null) {
|
|
valeurTotale += (stock * price);
|
|
}
|
|
}
|
|
|
|
return valeurTotale;
|
|
} catch (e) {
|
|
print('Erreur lors du calcul de la valeur totale du stock: $e');
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
// --- STATISTIQUES ---
|
|
|
|
Future<Map<String, dynamic>> 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<void> _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<void> _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<void> _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<void> _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<void> 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<void> 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<void> 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<int> 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<List<Commande>> 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,
|
|
u.point_de_vente_id AS pointDeVenteId,
|
|
pv.nom AS pointDeVenteDesign
|
|
FROM commandes c
|
|
LEFT JOIN clients cl ON c.clientId = cl.id
|
|
LEFT JOIN users u ON c.commandeurId = u.id
|
|
LEFT JOIN points_de_vente pv ON u.point_de_vente_id = pv.id
|
|
ORDER BY c.dateCommande DESC
|
|
''');
|
|
return result.map((row) => Commande.fromMap(row.fields)).toList();
|
|
}
|
|
|
|
Future<Commande?> 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<int> updateCommande(Commande commande) async {
|
|
final db = await database;
|
|
final commandeMap = commande.toMap();
|
|
final id = commandeMap.remove('id');
|
|
final date = DateTime.parse(commandeMap['dateCommande']);
|
|
commandeMap['dateCommande'] =
|
|
DateFormat('yyyy-MM-dd HH:mm:ss').format(date);
|
|
|
|
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<int> 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<int> 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!;
|
|
}
|
|
|
|
// Méthode mise à jour pour récupérer les détails avec les remises
|
|
Future<List<DetailCommande>> 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.est_cadeau ASC, dc.id
|
|
''', [commandeId]);
|
|
|
|
return result.map((row) => DetailCommande.fromMap(row.fields)).toList();
|
|
}
|
|
|
|
// --- RECHERCHE PRODUITS ---
|
|
|
|
Future<Product?> 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<Product?> 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<List<String>> 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<List<Product>> 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<int> 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<int> 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<List<Client>> 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<Client?> 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<Client?> 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<List<Users>> 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<Users?> 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<int> 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<void> 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<void> 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<bool> 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<bool> 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<int> 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<List<Product>> 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<int> 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<int> 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<int> deletePointDeVente(int id) async {
|
|
// final db = await database;
|
|
// final result = await db.query('DELETE FROM points_de_vente WHERE id = ?', [id]);
|
|
// return result.affectedRows!;
|
|
// }
|
|
// Dans votre classe AppDatabase, remplacez la méthode deletePointDeVente par ceci :
|
|
|
|
// SOLUTION 1: Vérification avant suppression avec gestion des produits
|
|
Future<int> deletePointDeVente(int id) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 1. Vérifier s'il y a des produits liés à ce point de vente
|
|
final produitsLies = await db.query(
|
|
'SELECT COUNT(*) as count FROM products WHERE point_de_vente_id = ?',
|
|
[id]);
|
|
final nombreProduits = produitsLies.first['count'] as int;
|
|
|
|
if (nombreProduits > 0) {
|
|
// Option A: Transférer les produits vers un point de vente par défaut
|
|
// Récupérer le premier point de vente disponible (ou créer un "point de vente général")
|
|
final pointsDeVente = await db.query(
|
|
'SELECT id FROM points_de_vente WHERE id != ? ORDER BY id ASC LIMIT 1',
|
|
[id]);
|
|
|
|
if (pointsDeVente.isNotEmpty) {
|
|
final pointDeVenteParDefaut = pointsDeVente.first['id'] as int;
|
|
|
|
// Transférer tous les produits vers le point de vente par défaut
|
|
await db.query(
|
|
'UPDATE products SET point_de_vente_id = ? WHERE point_de_vente_id = ?',
|
|
[pointDeVenteParDefaut, id]);
|
|
|
|
print(
|
|
"$nombreProduits produits transférés vers le point de vente ID $pointDeVenteParDefaut");
|
|
} else {
|
|
// Si aucun autre point de vente, créer un point de vente "Général"
|
|
final result = await db.query(
|
|
'INSERT INTO points_de_vente (nom) VALUES (?)', ['Général']);
|
|
final nouveauPointId = result.insertId!;
|
|
|
|
await db.query(
|
|
'UPDATE products SET point_de_vente_id = ? WHERE point_de_vente_id = ?',
|
|
[nouveauPointId, id]);
|
|
|
|
print(
|
|
"Point de vente 'Général' créé et $nombreProduits produits transférés");
|
|
}
|
|
}
|
|
|
|
// 2. Maintenant supprimer le point de vente
|
|
final deleteResult =
|
|
await db.query('DELETE FROM points_de_vente WHERE id = ?', [id]);
|
|
|
|
await db.query('COMMIT');
|
|
return deleteResult.affectedRows!;
|
|
} catch (e) {
|
|
await db.query('ROLLBACK');
|
|
print('Erreur lors de la suppression du point de vente: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// SOLUTION 2: Méthode alternative avec suppression douce (soft delete)
|
|
Future<int> deletePointDeVenteSoft(int id) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Ajouter une colonne 'actif' si elle n'existe pas déjà
|
|
// Cette solution nécessite d'ajouter une colonne, mais c'est moins invasif
|
|
|
|
// Pour l'instant, on peut utiliser une astuce en renommant le point de vente
|
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
|
final result = await db.query(
|
|
'UPDATE points_de_vente SET nom = CONCAT(nom, " (Supprimé ", ?, ")") WHERE id = ?',
|
|
[timestamp, id]);
|
|
|
|
return result.affectedRows!;
|
|
} catch (e) {
|
|
print('Erreur lors de la suppression douce: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// SOLUTION 3: Vérification avec message d'erreur personnalisé
|
|
Future<Map<String, dynamic>> checkCanDeletePointDeVente(int id) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Vérifier les produits
|
|
final produitsResult = await db.query(
|
|
'SELECT COUNT(*) as count FROM products WHERE point_de_vente_id = ?',
|
|
[id]);
|
|
final nombreProduits = produitsResult.first['count'] as int;
|
|
|
|
// Vérifier les utilisateurs
|
|
final usersResult = await db.query(
|
|
'SELECT COUNT(*) as count FROM users WHERE point_de_vente_id = ?',
|
|
[id]);
|
|
final nombreUsers = usersResult.first['count'] as int;
|
|
|
|
// Vérifier les demandes de transfert
|
|
final transfertsResult = await db.query('''
|
|
SELECT COUNT(*) as count FROM demandes_transfert
|
|
WHERE point_de_vente_source_id = ? OR point_de_vente_destination_id = ?
|
|
''', [id, id]);
|
|
final nombreTransferts = transfertsResult.first['count'] as int;
|
|
|
|
if (nombreProduits > 0 || nombreUsers > 0 || nombreTransferts > 0) {
|
|
return {
|
|
'canDelete': false,
|
|
'reasons': [
|
|
if (nombreProduits > 0) '$nombreProduits produit(s) associé(s)',
|
|
if (nombreUsers > 0) '$nombreUsers utilisateur(s) associé(s)',
|
|
if (nombreTransferts > 0)
|
|
'$nombreTransferts demande(s) de transfert associée(s)',
|
|
],
|
|
'suggestions': [
|
|
if (nombreProduits > 0)
|
|
'Transférez d\'abord les produits vers un autre point de vente',
|
|
if (nombreUsers > 0)
|
|
'Réassignez d\'abord les utilisateurs à un autre point de vente',
|
|
if (nombreTransferts > 0)
|
|
'Les demandes de transfert resteront pour l\'historique',
|
|
]
|
|
};
|
|
}
|
|
|
|
return {'canDelete': true, 'reasons': [], 'suggestions': []};
|
|
} catch (e) {
|
|
return {
|
|
'canDelete': false,
|
|
'reasons': ['Erreur lors de la vérification: $e'],
|
|
'suggestions': []
|
|
};
|
|
}
|
|
}
|
|
|
|
// SOLUTION 4: Suppression avec transfert automatique vers un point de vente spécifique
|
|
Future<int> deletePointDeVenteWithTransfer(
|
|
int idToDelete, int targetPointDeVenteId) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 1. Transférer tous les produits
|
|
await db.query(
|
|
'UPDATE products SET point_de_vente_id = ? WHERE point_de_vente_id = ?',
|
|
[targetPointDeVenteId, idToDelete]);
|
|
|
|
// 2. Transférer tous les utilisateurs (si applicable)
|
|
await db.query(
|
|
'UPDATE users SET point_de_vente_id = ? WHERE point_de_vente_id = ?',
|
|
[targetPointDeVenteId, idToDelete]);
|
|
|
|
// 3. Supprimer le point de vente
|
|
final result = await db
|
|
.query('DELETE FROM points_de_vente WHERE id = ?', [idToDelete]);
|
|
|
|
await db.query('COMMIT');
|
|
return result.affectedRows!;
|
|
} catch (e) {
|
|
await db.query('ROLLBACK');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// SOLUTION 5: Méthode pour obtenir les points de vente de destination possibles
|
|
Future<List<Map<String, dynamic>>> getPointsDeVenteForTransfer(
|
|
int excludeId) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query(
|
|
'SELECT * FROM points_de_vente WHERE id != ? ORDER BY nom ASC',
|
|
[excludeId]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération points de vente pour transfert: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>?> 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<int?> 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<String?> 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<List<Product>> searchProducts({
|
|
String? name,
|
|
String? imei,
|
|
String? reference,
|
|
bool onlyInStock = false,
|
|
String? category,
|
|
int? pointDeVenteId,
|
|
}) async {
|
|
final db = await database;
|
|
|
|
List<String> whereConditions = [];
|
|
List<dynamic> 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<Product?> 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 ---
|
|
|
|
// Méthode pour créer une commande complète avec remises
|
|
Future<int> createCommandeComplete(
|
|
Client client, Commande commande, List<DetailCommande> details) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 1. Créer ou récupérer le client
|
|
final existingOrNewClient = await createOrGetClient(client);
|
|
final clientId = existingOrNewClient.id!;
|
|
|
|
// 2. Créer la commande
|
|
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 avec remises
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Méthode pour mettre à jour un détail de commande (utile pour modifier les remises)
|
|
Future<int> updateDetailCommande(DetailCommande detail) async {
|
|
final db = await database;
|
|
final detailMap = detail.toMap();
|
|
final id = detailMap.remove('id');
|
|
|
|
final setClause = detailMap.keys.map((key) => '$key = ?').join(', ');
|
|
final values = [...detailMap.values, id];
|
|
|
|
final result = await db.query(
|
|
'UPDATE details_commandes SET $setClause WHERE id = ?', values);
|
|
return result.affectedRows!;
|
|
}
|
|
|
|
// Méthode pour obtenir les statistiques des remises
|
|
Future<Map<String, dynamic>> getRemiseStatistics() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Total des remises accordées
|
|
final totalRemisesResult = await db.query('''
|
|
SELECT
|
|
COUNT(*) as nombre_remises,
|
|
SUM(montant_remise) as total_remises,
|
|
AVG(montant_remise) as moyenne_remise
|
|
FROM details_commandes
|
|
WHERE remise_type IS NOT NULL AND montant_remise > 0
|
|
''');
|
|
|
|
// Remises par type
|
|
final remisesParTypeResult = await db.query('''
|
|
SELECT
|
|
remise_type,
|
|
COUNT(*) as nombre,
|
|
SUM(montant_remise) as total,
|
|
AVG(remise_valeur) as moyenne_valeur
|
|
FROM details_commandes
|
|
WHERE remise_type IS NOT NULL AND montant_remise > 0
|
|
GROUP BY remise_type
|
|
''');
|
|
|
|
// Produits avec le plus de remises
|
|
final produitsRemisesResult = await db.query('''
|
|
SELECT
|
|
p.name as produit_nom,
|
|
COUNT(*) as nombre_remises,
|
|
SUM(dc.montant_remise) as total_remises
|
|
FROM details_commandes dc
|
|
INNER JOIN products p ON dc.produitId = p.id
|
|
WHERE dc.remise_type IS NOT NULL AND dc.montant_remise > 0
|
|
GROUP BY dc.produitId, p.name
|
|
ORDER BY total_remises DESC
|
|
LIMIT 10
|
|
''');
|
|
|
|
return {
|
|
'total_remises': totalRemisesResult.first.fields,
|
|
'remises_par_type':
|
|
remisesParTypeResult.map((row) => row.fields).toList(),
|
|
'produits_remises':
|
|
produitsRemisesResult.map((row) => row.fields).toList(),
|
|
};
|
|
} catch (e) {
|
|
print("Erreur lors du calcul des statistiques de remises: $e");
|
|
return {
|
|
'total_remises': {
|
|
'nombre_remises': 0,
|
|
'total_remises': 0.0,
|
|
'moyenne_remise': 0.0
|
|
},
|
|
'remises_par_type': [],
|
|
'produits_remises': [],
|
|
};
|
|
}
|
|
}
|
|
|
|
// Méthode pour obtenir les commandes avec le plus de remises
|
|
Future<List<Map<String, dynamic>>> getCommandesAvecRemises(
|
|
{int limit = 20}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT
|
|
c.id as commande_id,
|
|
c.dateCommande,
|
|
c.montantTotal,
|
|
cl.nom as client_nom,
|
|
cl.prenom as client_prenom,
|
|
SUM(dc.montant_remise) as total_remises,
|
|
COUNT(CASE WHEN dc.remise_type IS NOT NULL THEN 1 END) as nombre_articles_remise,
|
|
COUNT(dc.id) as total_articles
|
|
FROM commandes c
|
|
INNER JOIN clients cl ON c.clientId = cl.id
|
|
INNER JOIN details_commandes dc ON c.id = dc.commandeId
|
|
GROUP BY c.id, c.dateCommande, c.montantTotal, cl.nom, cl.prenom
|
|
HAVING total_remises > 0
|
|
ORDER BY total_remises DESC
|
|
LIMIT ?
|
|
''', [limit]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print("Erreur lors de la récupération des commandes avec remises: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// --- STATISTIQUES AVANCÉES ---
|
|
|
|
Future<Map<String, int>> 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<Map<String, Map<String, int>>> 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<String, Map<String, int>> 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<List<Map<String, dynamic>>> 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<void> 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<Client?> 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<Client?> 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<List<Client>> 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<List<Client>> 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<Client> 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<List<Product>> 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<int> 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<List<Commande>> 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<List<Commande>> 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<int> 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<List<Map<String, dynamic>>> 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<Map<String, dynamic>?> 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<Map<String, dynamic>?> 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<int> 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<int> 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<int> 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<List<Map<String, dynamic>>> 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<bool> 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<Client?> findClientByAnyIdentifier({
|
|
String? email,
|
|
String? telephone,
|
|
String? nom,
|
|
String? prenom,
|
|
}) async {
|
|
// 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;
|
|
}
|
|
|
|
//
|
|
// Méthode pour obtenir les statistiques des cadeaux
|
|
Future<Map<String, dynamic>> getCadeauStatistics() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Total des cadeaux offerts
|
|
final totalCadeauxResult = await db.query('''
|
|
SELECT
|
|
COUNT(*) as nombre_cadeaux,
|
|
SUM(sousTotal) as valeur_totale_cadeaux,
|
|
AVG(sousTotal) as valeur_moyenne_cadeau,
|
|
SUM(quantite) as quantite_totale_cadeaux
|
|
FROM details_commandes
|
|
WHERE est_cadeau = 1
|
|
''');
|
|
|
|
// Cadeaux par produit
|
|
final cadeauxParProduitResult = await db.query('''
|
|
SELECT
|
|
p.name as produit_nom,
|
|
p.category as produit_categorie,
|
|
COUNT(*) as nombre_fois_offert,
|
|
SUM(dc.quantite) as quantite_totale_offerte,
|
|
SUM(dc.sousTotal) as valeur_totale_offerte
|
|
FROM details_commandes dc
|
|
INNER JOIN products p ON dc.produitId = p.id
|
|
WHERE dc.est_cadeau = 1
|
|
GROUP BY dc.produitId, p.name, p.category
|
|
ORDER BY quantite_totale_offerte DESC
|
|
LIMIT 10
|
|
''');
|
|
|
|
// Commandes avec cadeaux
|
|
final commandesAvecCadeauxResult = await db.query('''
|
|
SELECT
|
|
COUNT(DISTINCT c.id) as nombre_commandes_avec_cadeaux,
|
|
AVG(cadeau_stats.nombre_cadeaux_par_commande) as moyenne_cadeaux_par_commande,
|
|
AVG(cadeau_stats.valeur_cadeaux_par_commande) as valeur_moyenne_cadeaux_par_commande
|
|
FROM commandes c
|
|
INNER JOIN (
|
|
SELECT
|
|
commandeId,
|
|
COUNT(*) as nombre_cadeaux_par_commande,
|
|
SUM(sousTotal) as valeur_cadeaux_par_commande
|
|
FROM details_commandes
|
|
WHERE est_cadeau = 1
|
|
GROUP BY commandeId
|
|
) cadeau_stats ON c.id = cadeau_stats.commandeId
|
|
''');
|
|
|
|
// Évolution des cadeaux par mois
|
|
final evolutionMensuelleResult = await db.query('''
|
|
SELECT
|
|
DATE_FORMAT(c.dateCommande, '%Y-%m') as mois,
|
|
COUNT(dc.id) as nombre_cadeaux,
|
|
SUM(dc.sousTotal) as valeur_cadeaux
|
|
FROM details_commandes dc
|
|
INNER JOIN commandes c ON dc.commandeId = c.id
|
|
WHERE dc.est_cadeau = 1
|
|
AND c.dateCommande >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
|
|
GROUP BY DATE_FORMAT(c.dateCommande, '%Y-%m')
|
|
ORDER BY mois DESC
|
|
LIMIT 12
|
|
''');
|
|
|
|
return {
|
|
'total_cadeaux': totalCadeauxResult.first.fields,
|
|
'cadeaux_par_produit':
|
|
cadeauxParProduitResult.map((row) => row.fields).toList(),
|
|
'commandes_avec_cadeaux': commandesAvecCadeauxResult.first.fields,
|
|
'evolution_mensuelle':
|
|
evolutionMensuelleResult.map((row) => row.fields).toList(),
|
|
};
|
|
} catch (e) {
|
|
print("Erreur lors du calcul des statistiques de cadeaux: $e");
|
|
return {
|
|
'total_cadeaux': {
|
|
'nombre_cadeaux': 0,
|
|
'valeur_totale_cadeaux': 0.0,
|
|
'valeur_moyenne_cadeau': 0.0,
|
|
'quantite_totale_cadeaux': 0
|
|
},
|
|
'cadeaux_par_produit': [],
|
|
'commandes_avec_cadeaux': {
|
|
'nombre_commandes_avec_cadeaux': 0,
|
|
'moyenne_cadeaux_par_commande': 0.0,
|
|
'valeur_moyenne_cadeaux_par_commande': 0.0
|
|
},
|
|
'evolution_mensuelle': [],
|
|
};
|
|
}
|
|
}
|
|
|
|
// Méthode pour obtenir les commandes avec des cadeaux
|
|
Future<List<Map<String, dynamic>>> getCommandesAvecCadeaux(
|
|
{int limit = 20}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT
|
|
c.id as commande_id,
|
|
c.dateCommande,
|
|
c.montantTotal,
|
|
cl.nom as client_nom,
|
|
cl.prenom as client_prenom,
|
|
cadeau_stats.nombre_cadeaux,
|
|
cadeau_stats.valeur_cadeaux,
|
|
cadeau_stats.quantite_cadeaux,
|
|
(SELECT COUNT(*) FROM details_commandes WHERE commandeId = c.id) as total_articles
|
|
FROM commandes c
|
|
INNER JOIN clients cl ON c.clientId = cl.id
|
|
INNER JOIN (
|
|
SELECT
|
|
commandeId,
|
|
COUNT(*) as nombre_cadeaux,
|
|
SUM(sousTotal) as valeur_cadeaux,
|
|
SUM(quantite) as quantite_cadeaux
|
|
FROM details_commandes
|
|
WHERE est_cadeau = 1
|
|
GROUP BY commandeId
|
|
) cadeau_stats ON c.id = cadeau_stats.commandeId
|
|
ORDER BY cadeau_stats.valeur_cadeaux DESC
|
|
LIMIT ?
|
|
''', [limit]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print("Erreur lors de la récupération des commandes avec cadeaux: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Méthode pour obtenir les produits les plus offerts en cadeau
|
|
Future<List<Map<String, dynamic>>> getProduitsLesPlusOffertsEnCadeau(
|
|
{int limit = 10}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT
|
|
p.id,
|
|
p.name as produit_nom,
|
|
p.price as prix_unitaire,
|
|
p.category as categorie,
|
|
p.stock,
|
|
COUNT(dc.id) as nombre_fois_offert,
|
|
SUM(dc.quantite) as quantite_totale_offerte,
|
|
SUM(dc.sousTotal) as valeur_totale_offerte,
|
|
COUNT(DISTINCT dc.commandeId) as nombre_commandes_distinctes
|
|
FROM products p
|
|
INNER JOIN details_commandes dc ON p.id = dc.produitId
|
|
WHERE dc.est_cadeau = 1
|
|
GROUP BY p.id, p.name, p.price, p.category, p.stock
|
|
ORDER BY quantite_totale_offerte DESC
|
|
LIMIT ?
|
|
''', [limit]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print("Erreur lors de la récupération des produits les plus offerts: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Méthode pour obtenir les clients qui ont reçu le plus de cadeaux
|
|
Future<List<Map<String, dynamic>>> getClientsAvecLePlusDeCadeaux(
|
|
{int limit = 10}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT
|
|
cl.id as client_id,
|
|
cl.nom,
|
|
cl.prenom,
|
|
cl.email,
|
|
cl.telephone,
|
|
COUNT(dc.id) as nombre_cadeaux_recus,
|
|
SUM(dc.quantite) as quantite_cadeaux_recus,
|
|
SUM(dc.sousTotal) as valeur_cadeaux_recus,
|
|
COUNT(DISTINCT c.id) as nombre_commandes_avec_cadeaux
|
|
FROM clients cl
|
|
INNER JOIN commandes c ON cl.id = c.clientId
|
|
INNER JOIN details_commandes dc ON c.id = dc.commandeId
|
|
WHERE dc.est_cadeau = 1
|
|
GROUP BY cl.id, cl.nom, cl.prenom, cl.email, cl.telephone
|
|
ORDER BY valeur_cadeaux_recus DESC
|
|
LIMIT ?
|
|
''', [limit]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print(
|
|
"Erreur lors de la récupération des clients avec le plus de cadeaux: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Méthode pour calculer l'impact des cadeaux sur les ventes
|
|
Future<Map<String, dynamic>> getImpactCadeauxSurVentes() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Comparaison des commandes avec et sans cadeaux
|
|
final comparisonResult = await db.query('''
|
|
SELECT
|
|
'avec_cadeaux' as type_commande,
|
|
COUNT(DISTINCT c.id) as nombre_commandes,
|
|
AVG(c.montantTotal) as panier_moyen,
|
|
SUM(c.montantTotal) as chiffre_affaires_total
|
|
FROM commandes c
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM details_commandes dc
|
|
WHERE dc.commandeId = c.id AND dc.est_cadeau = 1
|
|
)
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
'sans_cadeaux' as type_commande,
|
|
COUNT(DISTINCT c.id) as nombre_commandes,
|
|
AVG(c.montantTotal) as panier_moyen,
|
|
SUM(c.montantTotal) as chiffre_affaires_total
|
|
FROM commandes c
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM details_commandes dc
|
|
WHERE dc.commandeId = c.id AND dc.est_cadeau = 1
|
|
)
|
|
''');
|
|
|
|
// Ratio de conversion (commandes avec cadeaux / total commandes)
|
|
final ratioResult = await db.query('''
|
|
SELECT
|
|
(SELECT COUNT(DISTINCT c.id)
|
|
FROM commandes c
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM details_commandes dc
|
|
WHERE dc.commandeId = c.id AND dc.est_cadeau = 1
|
|
)
|
|
) * 100.0 / COUNT(*) as pourcentage_commandes_avec_cadeaux
|
|
FROM commandes
|
|
''');
|
|
|
|
return {
|
|
'comparaison': comparisonResult.map((row) => row.fields).toList(),
|
|
'pourcentage_commandes_avec_cadeaux':
|
|
ratioResult.first['pourcentage_commandes_avec_cadeaux'] ?? 0.0,
|
|
};
|
|
} catch (e) {
|
|
print("Erreur lors du calcul de l'impact des cadeaux: $e");
|
|
return {
|
|
'comparaison': [],
|
|
'pourcentage_commandes_avec_cadeaux': 0.0,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Méthode pour créer une commande complète avec cadeaux (mise à jour)
|
|
Future<int> createCommandeCompleteAvecCadeaux(
|
|
Client client, Commande commande, List<DetailCommande> details) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 1. Créer ou récupérer le client
|
|
final existingOrNewClient = await createOrGetClient(client);
|
|
final clientId = existingOrNewClient.id!;
|
|
|
|
// 2. Créer la commande
|
|
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 avec remises et cadeaux
|
|
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 (même pour les cadeaux)
|
|
await db.query('UPDATE products SET stock = stock - ? WHERE id = ?',
|
|
[detail.quantite, detail.produitId]);
|
|
}
|
|
|
|
await db.query('COMMIT');
|
|
|
|
// Log des cadeaux offerts (optionnel)
|
|
final cadeaux = details.where((d) => d.estCadeau).toList();
|
|
if (cadeaux.isNotEmpty) {
|
|
print("Cadeaux offerts dans la commande $commandeId:");
|
|
for (final cadeau in cadeaux) {
|
|
print(
|
|
" - ${cadeau.produitNom} x${cadeau.quantite} (valeur: ${cadeau.sousTotal.toStringAsFixed(2)} MGA)");
|
|
}
|
|
}
|
|
|
|
return commandeId;
|
|
} catch (e) {
|
|
await db.query('ROLLBACK');
|
|
print(
|
|
"Erreur lors de la création de la commande complète avec cadeaux: $e");
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// Méthode pour valider la disponibilité des cadeaux avant la commande
|
|
Future<List<String>> verifierDisponibiliteCadeaux(
|
|
List<DetailCommande> details) async {
|
|
final db = await database;
|
|
List<String> erreurs = [];
|
|
|
|
try {
|
|
for (final detail in details.where((d) => d.estCadeau)) {
|
|
final produit = await getProductById(detail.produitId);
|
|
|
|
if (produit == null) {
|
|
erreurs.add("Produit cadeau introuvable (ID: ${detail.produitId})");
|
|
continue;
|
|
}
|
|
|
|
if (produit.stock != null && produit.stock! < detail.quantite) {
|
|
erreurs.add(
|
|
"Stock insuffisant pour le cadeau: ${produit.name} (demandé: ${detail.quantite}, disponible: ${produit.stock})");
|
|
}
|
|
}
|
|
} catch (e) {
|
|
erreurs.add("Erreur lors de la vérification des cadeaux: $e");
|
|
}
|
|
|
|
return erreurs;
|
|
}
|
|
// --- MÉTHODES POUR LES VENTES PAR POINT DE VENTE ---
|
|
|
|
Future<List<Map<String, dynamic>>> getVentesParPointDeVente() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT
|
|
pv.id as point_vente_id,
|
|
pv.nom as point_vente_nom,
|
|
COUNT(DISTINCT c.id) as nombre_commandes,
|
|
COUNT(dc.id) as nombre_articles_vendus,
|
|
SUM(dc.quantite) as quantite_totale_vendue,
|
|
SUM(c.montantTotal) as chiffre_affaires,
|
|
AVG(c.montantTotal) as panier_moyen,
|
|
MIN(c.dateCommande) as premiere_vente,
|
|
MAX(c.dateCommande) as derniere_vente
|
|
FROM points_de_vente pv
|
|
LEFT JOIN products p ON pv.id = p.point_de_vente_id
|
|
LEFT JOIN details_commandes dc ON p.id = dc.produitId
|
|
LEFT JOIN commandes c ON dc.commandeId = c.id
|
|
WHERE c.statut != 5 -- Exclure les commandes annulées
|
|
GROUP BY pv.id, pv.nom
|
|
ORDER BY chiffre_affaires DESC
|
|
''');
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print("Erreur getVentesParPointDeVente: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
|
|
int pointDeVenteId,
|
|
{int limit = 5}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT
|
|
p.id,
|
|
p.name as produit_nom,
|
|
p.price as prix_unitaire,
|
|
p.category as categorie,
|
|
SUM(dc.quantite) as quantite_vendue,
|
|
SUM(dc.sousTotal) as chiffre_affaires_produit,
|
|
COUNT(DISTINCT dc.commandeId) as nombre_commandes
|
|
FROM products p
|
|
INNER JOIN details_commandes dc ON p.id = dc.produitId
|
|
INNER JOIN commandes c ON dc.commandeId = c.id
|
|
WHERE p.point_de_vente_id = ? AND c.statut != 5
|
|
GROUP BY p.id, p.name, p.price, p.category
|
|
ORDER BY quantite_vendue DESC
|
|
LIMIT ?
|
|
''', [pointDeVenteId, limit]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print("Erreur getTopProduitsParPointDeVente: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getVentesParPointDeVenteParMois(
|
|
int pointDeVenteId) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT
|
|
DATE_FORMAT(c.dateCommande, '%Y-%m') as mois,
|
|
COUNT(DISTINCT c.id) as nombre_commandes,
|
|
SUM(c.montantTotal) as chiffre_affaires,
|
|
SUM(dc.quantite) as quantite_vendue
|
|
FROM commandes c
|
|
INNER JOIN details_commandes dc ON c.id = dc.commandeId
|
|
INNER JOIN products p ON dc.produitId = p.id
|
|
WHERE p.point_de_vente_id = ?
|
|
AND c.statut != 5
|
|
AND c.dateCommande >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
|
|
GROUP BY DATE_FORMAT(c.dateCommande, '%Y-%m')
|
|
ORDER BY mois DESC
|
|
LIMIT 12
|
|
''', [pointDeVenteId]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print("Erreur getVentesParPointDeVenteParMois: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Dans la classe AppDatabase, ajoutez cette méthode :
|
|
Future<bool> verifyCurrentUserPassword(String password) async {
|
|
final db = await database;
|
|
final userController = Get.find<UserController>();
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT COUNT(*) as count
|
|
FROM users
|
|
WHERE id = ? AND password = ?
|
|
''', [userController.userId, password]);
|
|
|
|
return (result.first['count'] as int) > 0;
|
|
} catch (e) {
|
|
print("Erreur lors de la vérification du mot de passe: $e");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Dans AppDatabase
|
|
Future<int> createDemandeTransfert({
|
|
required int produitId,
|
|
required int pointDeVenteSourceId,
|
|
required int pointDeVenteDestinationId,
|
|
required int demandeurId,
|
|
int quantite = 1,
|
|
String? notes,
|
|
}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
INSERT INTO demandes_transfert (
|
|
produit_id,
|
|
point_de_vente_source_id,
|
|
point_de_vente_destination_id,
|
|
demandeur_id,
|
|
quantite,
|
|
statut,
|
|
date_demande,
|
|
notes
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', [
|
|
produitId,
|
|
pointDeVenteSourceId,
|
|
pointDeVenteDestinationId,
|
|
demandeurId,
|
|
quantite,
|
|
'en_attente', // Statut initial
|
|
DateTime.now().toUtc(),
|
|
notes,
|
|
]);
|
|
|
|
return result.insertId!;
|
|
} catch (e) {
|
|
print('Erreur création demande transfert: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getDemandesTransfertEnAttente() async {
|
|
final db = await database;
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT dt.*,
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
p.stock as stock_source, -- AJOUT : récupérer le stock du produit
|
|
pv_source.nom as point_vente_source,
|
|
pv_dest.nom as point_vente_destination,
|
|
u.name as demandeur_nom
|
|
FROM demandes_transfert dt
|
|
JOIN products p ON dt.produit_id = p.id
|
|
JOIN points_de_vente pv_source ON dt.point_de_vente_source_id = pv_source.id
|
|
JOIN points_de_vente pv_dest ON dt.point_de_vente_destination_id = pv_dest.id
|
|
JOIN users u ON dt.demandeur_id = u.id
|
|
WHERE dt.statut = 'en_attente'
|
|
ORDER BY dt.date_demande DESC
|
|
''');
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération demandes transfert: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<int> validerTransfert(int demandeId, int validateurId) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 1. Récupérer les infos de la demande
|
|
final demande = await db.query(
|
|
'SELECT * FROM demandes_transfert WHERE id = ? FOR UPDATE',
|
|
[demandeId]);
|
|
|
|
if (demande.isEmpty) {
|
|
throw Exception('Demande de transfert introuvable');
|
|
}
|
|
|
|
final fields = demande.first.fields;
|
|
final produitId = fields['produit_id'] as int;
|
|
final quantite = fields['quantite'] as int;
|
|
final sourceId = fields['point_de_vente_source_id'] as int;
|
|
final destinationId = fields['point_de_vente_destination_id'] as int;
|
|
|
|
final getpointDeventeSource = await db.query(
|
|
'Select point_de_vente_source_id FROM demandes_transfert WHERE id=?',
|
|
[demandeId]);
|
|
final getpointDeventeDest = await db.query(
|
|
'Select point_de_vente_destination_id FROM demandes_transfert WHERE id=?',
|
|
[demandeId]);
|
|
final getpointDeventeSourceValue =
|
|
getpointDeventeSource.first.fields['point_de_vente_source_id'];
|
|
final getpointDeventedestValue =
|
|
getpointDeventeDest.first.fields['point_de_vente_destination_id'];
|
|
if (getpointDeventeSourceValue == getpointDeventedestValue) {
|
|
await db.query('update products set point_de_vente_id=? where id = ?',
|
|
[getpointDeventedestValue, produitId]);
|
|
} else {
|
|
// 2. Vérifier le stock source
|
|
final stockSource = await db.query(
|
|
'SELECT stock FROM products WHERE id = ? AND point_de_vente_id = ? FOR UPDATE',
|
|
[produitId, sourceId]);
|
|
|
|
if (stockSource.isEmpty) {
|
|
throw Exception('Produit introuvable dans le point de vente source');
|
|
}
|
|
|
|
final stockDisponible = stockSource.first['stock'] as int;
|
|
if (stockDisponible < quantite) {
|
|
throw Exception('Stock insuffisant dans le point de vente source');
|
|
}
|
|
|
|
// 3. Mettre à jour le stock source
|
|
await db.query(
|
|
'UPDATE products SET stock = stock - ? WHERE id = ? AND point_de_vente_id = ?',
|
|
[quantite, produitId, sourceId]);
|
|
|
|
// 4. Vérifier si le produit existe déjà dans le point de vente destination
|
|
final produitDestination = await db.query(
|
|
'SELECT id, stock FROM products WHERE id = ? AND point_de_vente_id = ?',
|
|
[produitId, destinationId]);
|
|
|
|
if (produitDestination.isNotEmpty) {
|
|
// Mettre à jour le stock existant
|
|
await db.query(
|
|
'UPDATE products SET stock = stock + ? WHERE id = ? AND point_de_vente_id = ?',
|
|
[quantite, produitId, destinationId]);
|
|
} else {
|
|
// Créer une copie du produit dans le nouveau point de vente
|
|
final produit = await db
|
|
.query('SELECT * FROM products WHERE id = ?', [produitId]);
|
|
|
|
if (produit.isEmpty) {
|
|
throw Exception('Produit introuvable');
|
|
}
|
|
|
|
final produitFields = produit.first.fields;
|
|
await db.query('''
|
|
INSERT INTO products (
|
|
name, price, image, category, stock, description,
|
|
qrCode, reference, point_de_vente_id, marque,
|
|
ram, memoire_interne, imei
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', [
|
|
produitFields['name'],
|
|
produitFields['price'],
|
|
produitFields['image'],
|
|
produitFields['category'],
|
|
quantite, // Nouveau stock
|
|
produitFields['description'],
|
|
produitFields['qrCode'],
|
|
produitFields['reference'],
|
|
destinationId,
|
|
produitFields['marque'],
|
|
produitFields['ram'],
|
|
produitFields['memoire_interne'],
|
|
null, // IMEI doit être unique donc on ne le copie pas
|
|
]);
|
|
}
|
|
}
|
|
// 5. Mettre à jour le statut de la demande
|
|
await db.query('''
|
|
UPDATE demandes_transfert
|
|
SET
|
|
statut = 'validee',
|
|
validateur_id = ?,
|
|
date_validation = ?
|
|
WHERE id = ?
|
|
''', [validateurId, DateTime.now().toUtc(), demandeId]);
|
|
|
|
await db.query('COMMIT');
|
|
return 1;
|
|
} catch (e) {
|
|
await db.query('ROLLBACK');
|
|
print('Erreur validation transfert: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
// Ajoutez ces méthodes dans votre classe AppDatabase
|
|
|
|
// 1. Méthode pour récupérer les demandes de transfert validées
|
|
Future<List<Map<String, dynamic>>> getDemandesTransfertValidees() async {
|
|
final db = await database;
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT dt.*,
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
p.stock as stock_source,
|
|
pv_source.nom as point_vente_source,
|
|
pv_dest.nom as point_vente_destination,
|
|
u_demandeur.name as demandeur_nom,
|
|
u_validateur.name as validateur_nom,
|
|
u_validateur.lastname as validateur_lastname
|
|
FROM demandes_transfert dt
|
|
JOIN products p ON dt.produit_id = p.id
|
|
JOIN points_de_vente pv_source ON dt.point_de_vente_source_id = pv_source.id
|
|
JOIN points_de_vente pv_dest ON dt.point_de_vente_destination_id = pv_dest.id
|
|
JOIN users u_demandeur ON dt.demandeur_id = u_demandeur.id
|
|
LEFT JOIN users u_validateur ON dt.validateur_id = u_validateur.id
|
|
WHERE dt.statut = 'validee'
|
|
ORDER BY dt.date_validation DESC
|
|
''');
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération demandes transfert validées: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 2. Méthode pour récupérer toutes les demandes de transfert
|
|
Future<List<Map<String, dynamic>>> getToutesDemandesTransfert() async {
|
|
final db = await database;
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT dt.*,
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
p.stock as stock_source,
|
|
pv_source.nom as point_vente_source,
|
|
pv_dest.nom as point_vente_destination,
|
|
u_demandeur.name as demandeur_nom,
|
|
u_validateur.name as validateur_nom,
|
|
u_validateur.lastname as validateur_lastname
|
|
FROM demandes_transfert dt
|
|
JOIN products p ON dt.produit_id = p.id
|
|
JOIN points_de_vente pv_source ON dt.point_de_vente_source_id = pv_source.id
|
|
JOIN points_de_vente pv_dest ON dt.point_de_vente_destination_id = pv_dest.id
|
|
JOIN users u_demandeur ON dt.demandeur_id = u_demandeur.id
|
|
LEFT JOIN users u_validateur ON dt.validateur_id = u_validateur.id
|
|
ORDER BY
|
|
CASE dt.statut
|
|
WHEN 'en_attente' THEN 1
|
|
WHEN 'validee' THEN 2
|
|
WHEN 'refusee' THEN 3
|
|
END,
|
|
dt.date_demande DESC
|
|
''');
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération toutes demandes transfert: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 3. Méthode pour rejeter une demande de transfert
|
|
Future<int> rejeterTransfert(
|
|
int demandeId, int validateurId, String motif) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// Vérifier que la demande existe et est en attente
|
|
final demande = await db.query(
|
|
'SELECT * FROM demandes_transfert WHERE id = ? AND statut = ? FOR UPDATE',
|
|
[demandeId, 'en_attente']);
|
|
|
|
if (demande.isEmpty) {
|
|
throw Exception('Demande de transfert introuvable ou déjà traitée');
|
|
}
|
|
|
|
// Mettre à jour le statut de la demande
|
|
final result = await db.query('''
|
|
UPDATE demandes_transfert
|
|
SET
|
|
statut = 'refusee',
|
|
validateur_id = ?,
|
|
date_validation = ?,
|
|
notes = CONCAT(COALESCE(notes, ''),
|
|
CASE WHEN notes IS NULL OR notes = '' THEN '' ELSE '\n--- REJET ---\n' END,
|
|
'Rejetée le ', ?, ' par validateur ID ', ?, ': ', ?)
|
|
WHERE id = ?
|
|
''', [
|
|
validateurId,
|
|
DateTime.now().toUtc(),
|
|
DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now()),
|
|
validateurId,
|
|
motif,
|
|
demandeId
|
|
]);
|
|
|
|
await db.query('COMMIT');
|
|
|
|
print(
|
|
'Demande de transfert $demandeId rejetée par l\'utilisateur $validateurId');
|
|
print('Motif: $motif');
|
|
|
|
return result.affectedRows!;
|
|
} catch (e) {
|
|
await db.query('ROLLBACK');
|
|
print('Erreur rejet transfert: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// 4. Méthode supplémentaire : récupérer les demandes de transfert refusées
|
|
Future<List<Map<String, dynamic>>> getDemandesTransfertRefusees() async {
|
|
final db = await database;
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT dt.*,
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
p.stock as stock_source,
|
|
pv_source.nom as point_vente_source,
|
|
pv_dest.nom as point_vente_destination,
|
|
u_demandeur.name as demandeur_nom,
|
|
u_validateur.name as validateur_nom,
|
|
u_validateur.lastname as validateur_lastname
|
|
FROM demandes_transfert dt
|
|
JOIN products p ON dt.produit_id = p.id
|
|
JOIN points_de_vente pv_source ON dt.point_de_vente_source_id = pv_source.id
|
|
JOIN points_de_vente pv_dest ON dt.point_de_vente_destination_id = pv_dest.id
|
|
JOIN users u_demandeur ON dt.demandeur_id = u_demandeur.id
|
|
LEFT JOIN users u_validateur ON dt.validateur_id = u_validateur.id
|
|
WHERE dt.statut = 'refusee'
|
|
ORDER BY dt.date_validation DESC
|
|
''');
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération demandes transfert refusées: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 5. Méthode pour récupérer les demandes par statut spécifique
|
|
Future<List<Map<String, dynamic>>> getDemandesTransfertParStatut(
|
|
String statut) async {
|
|
final db = await database;
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT dt.*,
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
p.stock as stock_source,
|
|
pv_source.nom as point_vente_source,
|
|
pv_dest.nom as point_vente_destination,
|
|
u_demandeur.name as demandeur_nom,
|
|
u_validateur.name as validateur_nom,
|
|
u_validateur.lastname as validateur_lastname
|
|
FROM demandes_transfert dt
|
|
JOIN products p ON dt.produit_id = p.id
|
|
JOIN points_de_vente pv_source ON dt.point_de_vente_source_id = pv_source.id
|
|
JOIN points_de_vente pv_dest ON dt.point_de_vente_destination_id = pv_dest.id
|
|
JOIN users u_demandeur ON dt.demandeur_id = u_demandeur.id
|
|
LEFT JOIN users u_validateur ON dt.validateur_id = u_validateur.id
|
|
WHERE dt.statut = ?
|
|
ORDER BY
|
|
CASE
|
|
WHEN dt.statut = 'en_attente' THEN dt.date_demande
|
|
ELSE dt.date_validation
|
|
END DESC
|
|
''', [statut]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération demandes transfert par statut: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 6. Méthode pour récupérer les statistiques des transferts
|
|
Future<Map<String, dynamic>> getStatistiquesTransferts() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Statistiques générales
|
|
final statsGenerales = await db.query('''
|
|
SELECT
|
|
COUNT(*) as total_demandes,
|
|
SUM(CASE WHEN statut = 'en_attente' THEN 1 ELSE 0 END) as en_attente,
|
|
SUM(CASE WHEN statut = 'validee' THEN 1 ELSE 0 END) as validees,
|
|
SUM(CASE WHEN statut = 'refusee' THEN 1 ELSE 0 END) as refusees,
|
|
SUM(CASE WHEN statut = 'validee' THEN quantite ELSE 0 END) as quantite_totale_transferee
|
|
FROM demandes_transfert
|
|
''');
|
|
|
|
// Top des produits les plus transférés
|
|
final topProduits = await db.query('''
|
|
SELECT
|
|
p.name as produit_nom,
|
|
p.category as categorie,
|
|
COUNT(*) as nombre_demandes,
|
|
SUM(dt.quantite) as quantite_totale,
|
|
SUM(CASE WHEN dt.statut = 'validee' THEN dt.quantite ELSE 0 END) as quantite_validee
|
|
FROM demandes_transfert dt
|
|
JOIN products p ON dt.produit_id = p.id
|
|
GROUP BY dt.produit_id, p.name, p.category
|
|
ORDER BY quantite_totale DESC
|
|
LIMIT 10
|
|
''');
|
|
|
|
// Points de vente les plus actifs
|
|
final topPointsVente = await db.query('''
|
|
SELECT
|
|
pv.nom as point_vente,
|
|
COUNT(dt_source.id) as demandes_sortantes,
|
|
COUNT(dt_dest.id) as demandes_entrantes,
|
|
(COUNT(dt_source.id) + COUNT(dt_dest.id)) as total_activite
|
|
FROM points_de_vente pv
|
|
LEFT JOIN demandes_transfert dt_source ON pv.id = dt_source.point_de_vente_source_id
|
|
LEFT JOIN demandes_transfert dt_dest ON pv.id = dt_dest.point_de_vente_destination_id
|
|
WHERE (dt_source.id IS NOT NULL OR dt_dest.id IS NOT NULL)
|
|
GROUP BY pv.id, pv.nom
|
|
ORDER BY total_activite DESC
|
|
LIMIT 10
|
|
''');
|
|
|
|
return {
|
|
'stats_generales': statsGenerales.first.fields,
|
|
'top_produits': topProduits.map((row) => row.fields).toList(),
|
|
'top_points_vente': topPointsVente.map((row) => row.fields).toList(),
|
|
};
|
|
} catch (e) {
|
|
print('Erreur récupération statistiques transferts: $e');
|
|
return {
|
|
'stats_generales': {
|
|
'total_demandes': 0,
|
|
'en_attente': 0,
|
|
'validees': 0,
|
|
'refusees': 0,
|
|
'quantite_totale_transferee': 0
|
|
},
|
|
'top_produits': [],
|
|
'top_points_vente': [],
|
|
};
|
|
}
|
|
}
|
|
|
|
// 7. Méthode pour récupérer l'historique des transferts d'un produit
|
|
Future<List<Map<String, dynamic>>> getHistoriqueTransfertsProduit(
|
|
int produitId) async {
|
|
final db = await database;
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT dt.*,
|
|
pv_source.nom as point_vente_source,
|
|
pv_dest.nom as point_vente_destination,
|
|
u_demandeur.name as demandeur_nom,
|
|
u_validateur.name as validateur_nom
|
|
FROM demandes_transfert dt
|
|
JOIN points_de_vente pv_source ON dt.point_de_vente_source_id = pv_source.id
|
|
JOIN points_de_vente pv_dest ON dt.point_de_vente_destination_id = pv_dest.id
|
|
JOIN users u_demandeur ON dt.demandeur_id = u_demandeur.id
|
|
LEFT JOIN users u_validateur ON dt.validateur_id = u_validateur.id
|
|
WHERE dt.produit_id = ?
|
|
ORDER BY dt.date_demande DESC
|
|
''', [produitId]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération historique transferts produit: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 8. Méthode pour annuler une demande de transfert (si en attente)
|
|
Future<int> annulerDemandeTransfert(int demandeId, int utilisateurId) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Vérifier que la demande existe et est en attente
|
|
final demande = await db.query(
|
|
'SELECT * FROM demandes_transfert WHERE id = ? AND statut = ? AND demandeur_id = ?',
|
|
[demandeId, 'en_attente', utilisateurId]);
|
|
|
|
if (demande.isEmpty) {
|
|
throw Exception(
|
|
'Demande introuvable, déjà traitée, ou vous n\'êtes pas autorisé à l\'annuler');
|
|
}
|
|
|
|
// Supprimer la demande (ou la marquer comme annulée si vous préférez garder l'historique)
|
|
final result = await db.query(
|
|
'DELETE FROM demandes_transfert WHERE id = ? AND statut = ? AND demandeur_id = ?',
|
|
[demandeId, 'en_attente', utilisateurId]);
|
|
|
|
return result.affectedRows!;
|
|
} catch (e) {
|
|
print('Erreur annulation demande transfert: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// --- MÉTHODES POUR SORTIES STOCK PERSONNELLES ---
|
|
|
|
Future<int> createSortieStockPersonnelle({
|
|
required int produitId,
|
|
required int adminId,
|
|
required int quantite,
|
|
required String motif,
|
|
int? pointDeVenteId,
|
|
String? notes,
|
|
}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 1. Vérifier que le produit existe et a assez de stock
|
|
final produit = await getProductById(produitId);
|
|
if (produit == null) {
|
|
throw Exception('Produit introuvable');
|
|
}
|
|
|
|
if (produit.stock != null && produit.stock! < quantite) {
|
|
throw Exception(
|
|
'Stock insuffisant (disponible: ${produit.stock}, demandé: $quantite)');
|
|
}
|
|
|
|
// 2. Créer la demande de sortie
|
|
final result = await db.query('''
|
|
INSERT INTO sorties_stock_personnelles (
|
|
produit_id,
|
|
admin_id,
|
|
quantite,
|
|
motif,
|
|
date_sortie,
|
|
point_de_vente_id,
|
|
notes,
|
|
statut
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', [
|
|
produitId,
|
|
adminId,
|
|
quantite,
|
|
motif,
|
|
DateTime.now().toUtc(),
|
|
pointDeVenteId,
|
|
notes,
|
|
'en_attente', // Par défaut en attente d'approbation
|
|
]);
|
|
|
|
await db.query('COMMIT');
|
|
return result.insertId!;
|
|
} catch (e) {
|
|
await db.query('ROLLBACK');
|
|
print('Erreur création sortie personnelle: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<int> approuverSortiePersonnelle(
|
|
int sortieId, int approbateurId) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
await db.query('START TRANSACTION');
|
|
|
|
// 1. Récupérer les détails de la sortie
|
|
final sortie = await db.query(
|
|
'SELECT * FROM sorties_stock_personnelles WHERE id = ? AND statut = ?',
|
|
[sortieId, 'en_attente']);
|
|
|
|
if (sortie.isEmpty) {
|
|
throw Exception('Sortie introuvable ou déjà traitée');
|
|
}
|
|
|
|
final fields = sortie.first.fields;
|
|
final produitId = fields['produit_id'] as int;
|
|
final quantite = fields['quantite'] as int;
|
|
|
|
// 2. Vérifier le stock actuel
|
|
final produit = await getProductById(produitId);
|
|
if (produit == null) {
|
|
throw Exception('Produit introuvable');
|
|
}
|
|
|
|
if (produit.stock != null && produit.stock! < quantite) {
|
|
throw Exception('Stock insuffisant pour approuver cette sortie');
|
|
}
|
|
|
|
// 3. Décrémenter le stock
|
|
await db.query('UPDATE products SET stock = stock - ? WHERE id = ?',
|
|
[quantite, produitId]);
|
|
|
|
// 4. Marquer la sortie comme approuvée
|
|
await db.query('''
|
|
UPDATE sorties_stock_personnelles
|
|
SET
|
|
statut = 'approuvee',
|
|
approbateur_id = ?,
|
|
date_approbation = ?
|
|
WHERE id = ?
|
|
''', [approbateurId, DateTime.now().toUtc(), sortieId]);
|
|
|
|
await db.query('COMMIT');
|
|
return 1;
|
|
} catch (e) {
|
|
await db.query('ROLLBACK');
|
|
print('Erreur approbation sortie: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<int> refuserSortiePersonnelle(
|
|
int sortieId, int approbateurId, String motifRefus) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
UPDATE sorties_stock_personnelles
|
|
SET
|
|
statut = 'refusee',
|
|
approbateur_id = ?,
|
|
date_approbation = ?,
|
|
notes = CONCAT(COALESCE(notes, ''), '\n--- REFUS ---\n', ?)
|
|
WHERE id = ? AND statut = 'en_attente'
|
|
''', [approbateurId, DateTime.now().toUtc(), motifRefus, sortieId]);
|
|
|
|
return result.affectedRows!;
|
|
} catch (e) {
|
|
print('Erreur refus sortie: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getSortiesPersonnellesEnAttente() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
final result = await db.query('''
|
|
SELECT sp.*,
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
p.stock as stock_actuel,
|
|
u_admin.name as admin_nom,
|
|
u_admin.lastname as admin_nom_famille,
|
|
pv.nom as point_vente_nom
|
|
FROM sorties_stock_personnelles sp
|
|
JOIN products p ON sp.produit_id = p.id
|
|
JOIN users u_admin ON sp.admin_id = u_admin.id
|
|
LEFT JOIN points_de_vente pv ON sp.point_de_vente_id = pv.id
|
|
WHERE sp.statut = 'en_attente'
|
|
ORDER BY sp.date_sortie DESC
|
|
''');
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération sorties en attente: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
|
|
int? adminId,
|
|
String? statut,
|
|
int limit = 50,
|
|
}) async {
|
|
final db = await database;
|
|
|
|
try {
|
|
String whereClause = '';
|
|
List<dynamic> params = [];
|
|
|
|
if (adminId != null) {
|
|
whereClause = 'WHERE sp.admin_id = ?';
|
|
params.add(adminId);
|
|
}
|
|
|
|
if (statut != null) {
|
|
whereClause +=
|
|
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.statut = ?';
|
|
params.add(statut);
|
|
}
|
|
|
|
final result = await db.query('''
|
|
SELECT sp.*,
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
u_admin.name as admin_nom,
|
|
u_admin.lastname as admin_nom_famille,
|
|
u_approb.name as approbateur_nom,
|
|
u_approb.lastname as approbateur_nom_famille,
|
|
pv.nom as point_vente_nom
|
|
FROM sorties_stock_personnelles sp
|
|
JOIN products p ON sp.produit_id = p.id
|
|
JOIN users u_admin ON sp.admin_id = u_admin.id
|
|
LEFT JOIN users u_approb ON sp.approbateur_id = u_approb.id
|
|
LEFT JOIN points_de_vente pv ON sp.point_de_vente_id = pv.id
|
|
$whereClause
|
|
ORDER BY sp.date_sortie DESC
|
|
LIMIT ?
|
|
''', [...params, limit]);
|
|
|
|
return result.map((row) => row.fields).toList();
|
|
} catch (e) {
|
|
print('Erreur récupération historique sorties: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>> getStatistiquesSortiesPersonnelles() async {
|
|
final db = await database;
|
|
|
|
try {
|
|
// Total des sorties par statut
|
|
final statsStatut = await db.query('''
|
|
SELECT
|
|
statut,
|
|
COUNT(*) as nombre,
|
|
SUM(quantite) as quantite_totale
|
|
FROM sorties_stock_personnelles
|
|
GROUP BY statut
|
|
''');
|
|
|
|
// Sorties par admin
|
|
final statsAdmin = await db.query('''
|
|
SELECT
|
|
u.name as admin_nom,
|
|
u.lastname as admin_nom_famille,
|
|
COUNT(*) as nombre_sorties,
|
|
SUM(sp.quantite) as quantite_totale
|
|
FROM sorties_stock_personnelles sp
|
|
JOIN users u ON sp.admin_id = u.id
|
|
WHERE sp.statut = 'approuvee'
|
|
GROUP BY u.id, u.name, u.lastname
|
|
ORDER BY quantite_totale DESC
|
|
LIMIT 10
|
|
''');
|
|
|
|
// Produits les plus sortis
|
|
final statsProduits = await db.query('''
|
|
SELECT
|
|
p.name as produit_nom,
|
|
p.reference as produit_reference,
|
|
SUM(sp.quantite) as quantite_sortie
|
|
FROM sorties_stock_personnelles sp
|
|
JOIN products p ON sp.produit_id = p.id
|
|
WHERE sp.statut = 'approuvee'
|
|
GROUP BY p.id, p.name, p.reference
|
|
ORDER BY quantite_sortie DESC
|
|
LIMIT 10
|
|
''');
|
|
|
|
return {
|
|
'stats_statut': statsStatut.map((row) => row.fields).toList(),
|
|
'stats_admin': statsAdmin.map((row) => row.fields).toList(),
|
|
'stats_produits': statsProduits.map((row) => row.fields).toList(),
|
|
};
|
|
} catch (e) {
|
|
print('Erreur statistiques sorties: $e');
|
|
return {
|
|
'stats_statut': [],
|
|
'stats_admin': [],
|
|
'stats_produits': [],
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|