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.
559 lines
17 KiB
559 lines
17 KiB
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:path/path.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
|
import '../Models/produit.dart';
|
|
import '../Models/client.dart';
|
|
|
|
|
|
class ProductDatabase {
|
|
static final ProductDatabase instance = ProductDatabase._init();
|
|
late Database _database;
|
|
|
|
ProductDatabase._init() {
|
|
sqfliteFfiInit();
|
|
}
|
|
|
|
ProductDatabase();
|
|
|
|
Future<Database> get database async {
|
|
if (_database.isOpen) return _database;
|
|
_database = await _initDB('products2.db');
|
|
return _database;
|
|
}
|
|
|
|
Future<void> initDatabase() async {
|
|
_database = await _initDB('products2.db');
|
|
await _createDB(_database, 1);
|
|
await _insertDefaultClients();
|
|
await _insertDefaultCommandes();
|
|
}
|
|
|
|
Future<Database> _initDB(String filePath) async {
|
|
final documentsDirectory = await getApplicationDocumentsDirectory();
|
|
final path = join(documentsDirectory.path, filePath);
|
|
|
|
bool dbExists = await File(path).exists();
|
|
if (!dbExists) {
|
|
try {
|
|
ByteData data = await rootBundle.load('assets/database/$filePath');
|
|
List<int> bytes =
|
|
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
|
|
await File(path).writeAsBytes(bytes);
|
|
} catch (e) {
|
|
print('Pas de fichier DB dans assets, création nouvelle DB');
|
|
}
|
|
}
|
|
|
|
return await databaseFactoryFfi.openDatabase(path);
|
|
}
|
|
|
|
Future<void> _createDB(Database db, int version) async {
|
|
final tables = await db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'");
|
|
final tableNames = tables.map((row) => row['name'] as String).toList();
|
|
|
|
// Table products (existante avec améliorations)
|
|
if (!tableNames.contains('products')) {
|
|
await db.execute('''
|
|
CREATE TABLE products(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
price REAL NOT NULL,
|
|
image TEXT,
|
|
category TEXT NOT NULL,
|
|
stock INTEGER NOT NULL DEFAULT 0,
|
|
description TEXT,
|
|
qrCode TEXT,
|
|
reference TEXT UNIQUE
|
|
)
|
|
''');
|
|
print("Table 'products' créée.");
|
|
} else {
|
|
// Vérifier et ajouter les colonnes manquantes
|
|
await _updateProductsTable(db);
|
|
}
|
|
|
|
// Table clients
|
|
if (!tableNames.contains('clients')) {
|
|
await db.execute('''
|
|
CREATE TABLE clients(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
nom TEXT NOT NULL,
|
|
prenom TEXT NOT NULL,
|
|
email TEXT NOT NULL UNIQUE,
|
|
telephone TEXT NOT NULL,
|
|
adresse TEXT,
|
|
dateCreation TEXT NOT NULL,
|
|
actif INTEGER NOT NULL DEFAULT 1
|
|
)
|
|
''');
|
|
print("Table 'clients' créée.");
|
|
}
|
|
|
|
// Table commandes
|
|
if (!tableNames.contains('commandes')) {
|
|
await db.execute('''
|
|
CREATE TABLE commandes(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
clientId INTEGER NOT NULL,
|
|
dateCommande TEXT NOT NULL,
|
|
statut INTEGER NOT NULL DEFAULT 0,
|
|
montantTotal REAL NOT NULL,
|
|
notes TEXT,
|
|
dateLivraison TEXT,
|
|
FOREIGN KEY (clientId) REFERENCES clients(id) ON DELETE CASCADE
|
|
)
|
|
''');
|
|
print("Table 'commandes' créée.");
|
|
}
|
|
|
|
// Table détails commandes
|
|
if (!tableNames.contains('details_commandes')) {
|
|
await db.execute('''
|
|
CREATE TABLE details_commandes(
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
commandeId INTEGER NOT NULL,
|
|
produitId INTEGER NOT NULL,
|
|
quantite INTEGER NOT NULL,
|
|
prixUnitaire REAL NOT NULL,
|
|
sousTotal REAL NOT NULL,
|
|
FOREIGN KEY (commandeId) REFERENCES commandes(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (produitId) REFERENCES products(id) ON DELETE CASCADE
|
|
)
|
|
''');
|
|
print("Table 'details_commandes' créée.");
|
|
}
|
|
|
|
// Créer les index pour optimiser les performances
|
|
await _createIndexes(db);
|
|
}
|
|
|
|
Future<void> _updateProductsTable(Database db) async {
|
|
final columns = await db.rawQuery('PRAGMA table_info(products)');
|
|
final columnNames = columns.map((e) => e['name'] as String).toList();
|
|
|
|
if (!columnNames.contains('description')) {
|
|
await db.execute("ALTER TABLE products ADD COLUMN description TEXT");
|
|
print("Colonne 'description' ajoutée.");
|
|
}
|
|
if (!columnNames.contains('qrCode')) {
|
|
await db.execute("ALTER TABLE products ADD COLUMN qrCode TEXT");
|
|
print("Colonne 'qrCode' ajoutée.");
|
|
}
|
|
if (!columnNames.contains('reference')) {
|
|
await db.execute("ALTER TABLE products ADD COLUMN reference TEXT");
|
|
print("Colonne 'reference' ajoutée.");
|
|
}
|
|
}
|
|
|
|
Future<void> _createIndexes(Database db) async {
|
|
await db.execute('CREATE INDEX IF NOT EXISTS idx_products_category ON products(category)');
|
|
await db.execute('CREATE INDEX IF NOT EXISTS idx_products_reference ON products(reference)');
|
|
await db.execute('CREATE INDEX IF NOT EXISTS idx_commandes_client ON commandes(clientId)');
|
|
await db.execute('CREATE INDEX IF NOT EXISTS idx_commandes_date ON commandes(dateCommande)');
|
|
await db.execute('CREATE INDEX IF NOT EXISTS idx_details_commande ON details_commandes(commandeId)');
|
|
print("Index créés pour optimiser les performances.");
|
|
}
|
|
|
|
// =========================
|
|
// MÉTHODES PRODUCTS (existantes)
|
|
// =========================
|
|
Future<int> createProduct(Product product) async {
|
|
final db = await database;
|
|
return await db.insert('products', product.toMap());
|
|
}
|
|
|
|
Future<List<Product>> getProducts() async {
|
|
final db = await database;
|
|
final maps = await db.query('products', orderBy: 'name ASC');
|
|
return List.generate(maps.length, (i) {
|
|
return Product.fromMap(maps[i]);
|
|
});
|
|
}
|
|
|
|
Future<int> updateProduct(Product product) async {
|
|
final db = await database;
|
|
return await db.update(
|
|
'products',
|
|
product.toMap(),
|
|
where: 'id = ?',
|
|
whereArgs: [product.id],
|
|
);
|
|
}
|
|
|
|
Future<int> deleteProduct(int? id) async {
|
|
final db = await database;
|
|
return await db.delete(
|
|
'products',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
Future<List<String>> getCategories() async {
|
|
final db = await database;
|
|
final result = await db.rawQuery('SELECT DISTINCT category FROM products ORDER BY category');
|
|
return List.generate(
|
|
result.length, (index) => result[index]['category'] as String);
|
|
}
|
|
|
|
Future<List<Product>> getProductsByCategory(String category) async {
|
|
final db = await database;
|
|
final maps = await db
|
|
.query('products', where: 'category = ?', whereArgs: [category], orderBy: 'name ASC');
|
|
return List.generate(maps.length, (i) {
|
|
return Product.fromMap(maps[i]);
|
|
});
|
|
}
|
|
|
|
Future<int> updateStock(int id, int stock) async {
|
|
final db = await database;
|
|
return await db
|
|
.rawUpdate('UPDATE products SET stock = ? WHERE id = ?', [stock, id]);
|
|
}
|
|
|
|
Future<Product?> getProductByReference(String reference) async {
|
|
final db = await database;
|
|
final maps = await db.query(
|
|
'products',
|
|
where: 'reference = ?',
|
|
whereArgs: [reference],
|
|
);
|
|
|
|
if (maps.isNotEmpty) {
|
|
return Product.fromMap(maps.first);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// =========================
|
|
// MÉTHODES CLIENTS
|
|
// =========================
|
|
Future<int> createClient(Client client) async {
|
|
final db = await database;
|
|
return await db.insert('clients', client.toMap());
|
|
}
|
|
|
|
Future<List<Client>> getClients() async {
|
|
final db = await database;
|
|
final maps = await db.query('clients', where: 'actif = 1', orderBy: 'nom ASC, prenom ASC');
|
|
return List.generate(maps.length, (i) {
|
|
return Client.fromMap(maps[i]);
|
|
});
|
|
}
|
|
|
|
Future<Client?> getClientById(int id) async {
|
|
final db = await database;
|
|
final maps = await db.query('clients', where: 'id = ?', whereArgs: [id]);
|
|
if (maps.isNotEmpty) {
|
|
return Client.fromMap(maps.first);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<int> updateClient(Client client) async {
|
|
final db = await database;
|
|
return await db.update(
|
|
'clients',
|
|
client.toMap(),
|
|
where: 'id = ?',
|
|
whereArgs: [client.id],
|
|
);
|
|
}
|
|
|
|
Future<int> deleteClient(int id) async {
|
|
final db = await database;
|
|
// Soft delete
|
|
return await db.update(
|
|
'clients',
|
|
{'actif': 0},
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
Future<List<Client>> searchClients(String query) async {
|
|
final db = await database;
|
|
final maps = await db.query(
|
|
'clients',
|
|
where: 'actif = 1 AND (nom LIKE ? OR prenom LIKE ? OR email LIKE ?)',
|
|
whereArgs: ['%$query%', '%$query%', '%$query%'],
|
|
orderBy: 'nom ASC, prenom ASC',
|
|
);
|
|
return List.generate(maps.length, (i) {
|
|
return Client.fromMap(maps[i]);
|
|
});
|
|
}
|
|
|
|
// =========================
|
|
// MÉTHODES COMMANDES
|
|
// =========================
|
|
Future<int> createCommande(Commande commande) async {
|
|
final db = await database;
|
|
return await db.insert('commandes', commande.toMap());
|
|
}
|
|
|
|
Future<List<Commande>> getCommandes() async {
|
|
final db = await database;
|
|
final maps = await db.rawQuery('''
|
|
SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail
|
|
FROM commandes c
|
|
LEFT JOIN clients cl ON c.clientId = cl.id
|
|
ORDER BY c.dateCommande DESC
|
|
''');
|
|
return List.generate(maps.length, (i) {
|
|
return Commande.fromMap(maps[i]);
|
|
});
|
|
}
|
|
|
|
Future<List<Commande>> getCommandesByClient(int clientId) async {
|
|
final db = await database;
|
|
final maps = await db.rawQuery('''
|
|
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 List.generate(maps.length, (i) {
|
|
return Commande.fromMap(maps[i]);
|
|
});
|
|
}
|
|
|
|
Future<Commande?> getCommandeById(int id) async {
|
|
final db = await database;
|
|
final maps = await db.rawQuery('''
|
|
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 (maps.isNotEmpty) {
|
|
return Commande.fromMap(maps.first);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Future<int> updateCommande(Commande commande) async {
|
|
final db = await database;
|
|
return await db.update(
|
|
'commandes',
|
|
commande.toMap(),
|
|
where: 'id = ?',
|
|
whereArgs: [commande.id],
|
|
);
|
|
}
|
|
|
|
Future<int> updateStatutCommande(int commandeId, StatutCommande statut) async {
|
|
final db = await database;
|
|
return await db.update(
|
|
'commandes',
|
|
{'statut': statut.index},
|
|
where: 'id = ?',
|
|
whereArgs: [commandeId],
|
|
);
|
|
}
|
|
|
|
// =========================
|
|
// MÉTHODES DÉTAILS COMMANDES
|
|
// =========================
|
|
Future<int> createDetailCommande(DetailCommande detail) async {
|
|
final db = await database;
|
|
return await db.insert('details_commandes', detail.toMap());
|
|
}
|
|
|
|
Future<List<DetailCommande>> getDetailsCommande(int commandeId) async {
|
|
final db = await database;
|
|
final maps = await db.rawQuery('''
|
|
SELECT dc.*, p.name as produitNom, p.image as produitImage, p.reference as produitReference
|
|
FROM details_commandes dc
|
|
LEFT JOIN products p ON dc.produitId = p.id
|
|
WHERE dc.commandeId = ?
|
|
ORDER BY dc.id
|
|
''', [commandeId]);
|
|
return List.generate(maps.length, (i) {
|
|
return DetailCommande.fromMap(maps[i]);
|
|
});
|
|
}
|
|
|
|
// =========================
|
|
// MÉTHODES TRANSACTION COMPLÈTE
|
|
// =========================
|
|
Future<int> createCommandeComplete(Client client, Commande commande, List<DetailCommande> details) async {
|
|
final db = await database;
|
|
|
|
return await db.transaction((txn) async {
|
|
// Créer le client
|
|
final clientId = await txn.insert('clients', client.toMap());
|
|
|
|
// Créer la commande
|
|
final commandeMap = commande.toMap();
|
|
commandeMap['clientId'] = clientId;
|
|
final commandeId = await txn.insert('commandes', commandeMap);
|
|
|
|
// Créer les détails et mettre à jour le stock
|
|
for (var detail in details) {
|
|
final detailMap = detail.toMap();
|
|
detailMap['commandeId'] = commandeId; // Ajoute l'ID de la commande
|
|
await txn.insert('details_commandes', detailMap);
|
|
|
|
// Mettre à jour le stock du produit
|
|
await txn.rawUpdate(
|
|
'UPDATE products SET stock = stock - ? WHERE id = ?',
|
|
[detail.quantite, detail.produitId],
|
|
);
|
|
}
|
|
|
|
return commandeId;
|
|
});
|
|
}
|
|
|
|
// =========================
|
|
// STATISTIQUES
|
|
// =========================
|
|
Future<Map<String, dynamic>> getStatistiques() async {
|
|
final db = await database;
|
|
|
|
final totalClients = await db.rawQuery('SELECT COUNT(*) as count FROM clients WHERE actif = 1');
|
|
final totalCommandes = await db.rawQuery('SELECT COUNT(*) as count FROM commandes');
|
|
final totalProduits = await db.rawQuery('SELECT COUNT(*) as count FROM products');
|
|
final chiffreAffaires = await db.rawQuery('SELECT SUM(montantTotal) as total FROM commandes WHERE statut != 5'); // 5 = annulée
|
|
|
|
return {
|
|
'totalClients': totalClients.first['count'],
|
|
'totalCommandes': totalCommandes.first['count'],
|
|
'totalProduits': totalProduits.first['count'],
|
|
'chiffreAffaires': chiffreAffaires.first['total'] ?? 0.0,
|
|
};
|
|
}
|
|
|
|
// =========================
|
|
// DONNÉES PAR DÉFAUT
|
|
// =========================
|
|
Future<void> _insertDefaultClients() async {
|
|
final db = await database;
|
|
final existingClients = await db.query('clients');
|
|
|
|
if (existingClients.isEmpty) {
|
|
final defaultClients = [
|
|
Client(
|
|
nom: 'Dupont',
|
|
prenom: 'Jean',
|
|
email: 'jean.dupont@email.com',
|
|
telephone: '0123456789',
|
|
adresse: '123 Rue de la Paix, Paris',
|
|
dateCreation: DateTime.now(),
|
|
),
|
|
Client(
|
|
nom: 'Martin',
|
|
prenom: 'Marie',
|
|
email: 'marie.martin@email.com',
|
|
telephone: '0987654321',
|
|
adresse: '456 Avenue des Champs, Lyon',
|
|
dateCreation: DateTime.now(),
|
|
),
|
|
Client(
|
|
nom: 'Bernard',
|
|
prenom: 'Pierre',
|
|
email: 'pierre.bernard@email.com',
|
|
telephone: '0456789123',
|
|
adresse: '789 Boulevard Saint-Michel, Marseille',
|
|
dateCreation: DateTime.now(),
|
|
),
|
|
];
|
|
|
|
for (var client in defaultClients) {
|
|
await db.insert('clients', client.toMap());
|
|
}
|
|
print("Clients par défaut insérés");
|
|
}
|
|
}
|
|
|
|
Future<void> _insertDefaultCommandes() async {
|
|
final db = await database;
|
|
final existingCommandes = await db.query('commandes');
|
|
|
|
if (existingCommandes.isEmpty) {
|
|
// Récupérer quelques produits pour créer des commandes
|
|
final produits = await db.query('products', limit: 3);
|
|
final clients = await db.query('clients', limit: 3);
|
|
|
|
if (produits.isNotEmpty && clients.isNotEmpty) {
|
|
// Commande 1
|
|
final commande1Id = await db.insert('commandes', {
|
|
'clientId': clients[0]['id'],
|
|
'dateCommande': DateTime.now().subtract(Duration(days: 5)).toIso8601String(),
|
|
'statut': StatutCommande.livree.index,
|
|
'montantTotal': 150.0,
|
|
'notes': 'Commande urgente',
|
|
});
|
|
|
|
await db.insert('details_commandes', {
|
|
'commandeId': commande1Id,
|
|
'produitId': produits[0]['id'],
|
|
'quantite': 2,
|
|
'prixUnitaire': 75.0,
|
|
'sousTotal': 150.0,
|
|
});
|
|
|
|
// Commande 2
|
|
final commande2Id = await db.insert('commandes', {
|
|
'clientId': clients[1]['id'],
|
|
'dateCommande': DateTime.now().subtract(Duration(days: 2)).toIso8601String(),
|
|
'statut': StatutCommande.enPreparation.index,
|
|
'montantTotal': 225.0,
|
|
'notes': 'Livraison prévue demain',
|
|
});
|
|
|
|
if (produits.length > 1) {
|
|
await db.insert('details_commandes', {
|
|
'commandeId': commande2Id,
|
|
'produitId': produits[1]['id'],
|
|
'quantite': 3,
|
|
'prixUnitaire': 75.0,
|
|
'sousTotal': 225.0,
|
|
});
|
|
}
|
|
|
|
// Commande 3
|
|
final commande3Id = await db.insert('commandes', {
|
|
'clientId': clients[2]['id'],
|
|
'dateCommande': DateTime.now().subtract(Duration(hours: 6)).toIso8601String(),
|
|
'statut': StatutCommande.confirmee.index,
|
|
'montantTotal': 300.0,
|
|
'notes': 'Commande standard',
|
|
});
|
|
|
|
if (produits.length > 2) {
|
|
await db.insert('details_commandes', {
|
|
'commandeId': commande3Id,
|
|
'produitId': produits[2]['id'],
|
|
'quantite': 4,
|
|
'prixUnitaire': 75.0,
|
|
'sousTotal': 300.0,
|
|
});
|
|
}
|
|
|
|
print("Commandes par défaut insérées");
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> close() async {
|
|
if (_database.isOpen) {
|
|
await _database.close();
|
|
}
|
|
}
|
|
// Ajoutez cette méthode temporaire pour supprimer la DB corrompue
|
|
Future<void> deleteDatabaseFile() async {
|
|
final documentsDirectory = await getApplicationDocumentsDirectory();
|
|
final path = join(documentsDirectory.path, 'products2.db');
|
|
final file = File(path);
|
|
if (await file.exists()) {
|
|
await file.delete();
|
|
print("Base de données product supprimée");
|
|
}
|
|
}
|
|
}
|