Browse Source

maj dernier farany farany

31052025_01
b.razafimandimbihery 6 months ago
parent
commit
57ea91b3d7
  1. 2
      android/app/src/main/AndroidManifest.xml
  2. 207
      lib/Components/QrScan.dart
  3. 4
      lib/Components/appDrawer.dart
  4. 100
      lib/Components/app_bar.dart
  5. 12
      lib/Models/Client.dart
  6. 4
      lib/Models/produit.dart
  7. 5
      lib/Models/users.dart
  8. 0
      lib/Services/GestionStockDatabase.dart
  9. 680
      lib/Services/app_database.dart
  10. 559
      lib/Services/productDatabase.dart
  11. 1139
      lib/Services/stock_managementDatabase.dart
  12. 9
      lib/Views/HandleProduct.dart
  13. 7
      lib/Views/RoleListPage.dart
  14. 3
      lib/Views/RolePermissionPage.dart
  15. 2
      lib/Views/bilanMois.dart
  16. 562
      lib/Views/commandManagement.dart
  17. 5
      lib/Views/editProduct.dart
  18. 3
      lib/Views/editUser.dart
  19. 7
      lib/Views/gestionProduct.dart
  20. 5
      lib/Views/gestionRole.dart
  21. 7
      lib/Views/gestionStock.dart
  22. 438
      lib/Views/historique.dart
  23. 2
      lib/Views/listCommandeHistory.dart
  24. 5
      lib/Views/listUser.dart
  25. 66
      lib/Views/loginPage.dart
  26. 837
      lib/Views/mobilepage.dart
  27. 519
      lib/Views/newCommand.dart
  28. 2
      lib/Views/produitsCard.dart
  29. 70
      lib/Views/registrationPage.dart
  30. 5
      lib/accueil.dart
  31. 5
      lib/controller/AccueilController.dart
  32. 63
      lib/controller/userController.dart
  33. 9
      lib/main.dart
  34. 2
      macos/Flutter/GeneratedPluginRegistrant.swift
  35. 8
      pubspec.lock
  36. 1
      pubspec.yaml

2
android/app/src/main/AndroidManifest.xml

@ -12,6 +12,7 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
@ -42,4 +43,5 @@
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain"/>
</intent> </intent>
</queries> </queries>
<uses-permission android:name="android.permission.CAMERA" />
</manifest> </manifest>

207
lib/Components/QrScan.dart

@ -0,0 +1,207 @@
// Ajoutez cette importation en haut du fichier
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
// Créez une nouvelle classe pour la page de scan QR
class ScanQRPage extends StatefulWidget {
const ScanQRPage({super.key});
@override
State<ScanQRPage> createState() => _ScanQRPageState();
}
class _ScanQRPageState extends State<ScanQRPage> {
MobileScannerController cameraController = MobileScannerController();
bool _isScanComplete = false;
String? _scannedData;
@override
void dispose() {
cameraController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scanner QR Code'),
actions: [
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: cameraController.torchState,
builder: (context, state, child) {
switch (state) {
case TorchState.off:
return const Icon(Icons.flash_off, color: Colors.grey);
case TorchState.on:
return const Icon(Icons.flash_on, color: Colors.yellow);
}
},
),
iconSize: 32.0,
onPressed: () => cameraController.toggleTorch(),
),
IconButton(
color: Colors.white,
icon: ValueListenableBuilder(
valueListenable: cameraController.cameraFacingState,
builder: (context, state, child) {
switch (state) {
case CameraFacing.front:
return const Icon(Icons.camera_front);
case CameraFacing.back:
return const Icon(Icons.camera_rear);
}
},
),
iconSize: 32.0,
onPressed: () => cameraController.switchCamera(),
),
],
),
body: Stack(
children: [
MobileScanner(
controller: cameraController,
onDetect: (capture) {
final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
if (!_isScanComplete && barcode.rawValue != null) {
_isScanComplete = true;
_scannedData = barcode.rawValue;
_showScanResult(context, _scannedData!);
}
}
},
),
CustomPaint(
painter: QrScannerOverlay(
borderColor: Colors.blue.shade800,
),
),
],
),
);
}
void _showScanResult(BuildContext context, String data) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Résultat du scan'),
content: Text(data),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
setState(() {
_isScanComplete = false;
});
},
child: const Text('OK'),
),
],
),
).then((_) {
// Réinitialiser le scan après la fermeture du dialogue
setState(() {
_isScanComplete = false;
});
});
}
}
// Widget personnalisé pour l'overlay du scanner
class QrScannerOverlay extends CustomPainter {
final Color borderColor;
QrScannerOverlay({required this.borderColor});
@override
void paint(Canvas canvas, Size size) {
final double width = size.width;
final double height = size.height;
final double borderWidth = 2.0;
final double borderLength = 30.0;
final double areaSize = width * 0.7;
// Dessiner un rectangle semi-transparent autour de la zone de scan
final Paint backgroundPaint = Paint()
..color = Colors.black.withOpacity(0.4);
canvas.drawRect(Rect.fromLTRB(0, 0, width, height), backgroundPaint);
// Dessiner la zone de scan transparente
final Paint transparentPaint = Paint()
..color = Colors.transparent
..blendMode = BlendMode.clear;
final double areaLeft = (width - areaSize) / 2;
final double areaTop = (height - areaSize) / 2;
canvas.drawRect(
Rect.fromLTRB(areaLeft, areaTop, areaLeft + areaSize, areaTop + areaSize),
transparentPaint,
);
// Dessiner les bordures de la zone de scan
final Paint borderPaint = Paint()
..color = borderColor
..strokeWidth = borderWidth
..style = PaintingStyle.stroke;
// Coin supérieur gauche
canvas.drawLine(
Offset(areaLeft, areaTop),
Offset(areaLeft + borderLength, areaTop),
borderPaint,
);
canvas.drawLine(
Offset(areaLeft, areaTop),
Offset(areaLeft, areaTop + borderLength),
borderPaint,
);
// Coin supérieur droit
canvas.drawLine(
Offset(areaLeft + areaSize - borderLength, areaTop),
Offset(areaLeft + areaSize, areaTop),
borderPaint,
);
canvas.drawLine(
Offset(areaLeft + areaSize, areaTop),
Offset(areaLeft + areaSize, areaTop + borderLength),
borderPaint,
);
// Coin inférieur gauche
canvas.drawLine(
Offset(areaLeft, areaTop + areaSize - borderLength),
Offset(areaLeft, areaTop + areaSize),
borderPaint,
);
canvas.drawLine(
Offset(areaLeft, areaTop + areaSize),
Offset(areaLeft + borderLength, areaTop + areaSize),
borderPaint,
);
// Coin inférieur droit
canvas.drawLine(
Offset(areaLeft + areaSize - borderLength, areaTop + areaSize),
Offset(areaLeft + areaSize, areaTop + areaSize),
borderPaint,
);
canvas.drawLine(
Offset(areaLeft + areaSize, areaTop + areaSize - borderLength),
Offset(areaLeft + areaSize, areaTop + areaSize),
borderPaint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}

4
lib/Components/appDrawer.dart

@ -280,6 +280,7 @@ class CustomDrawer extends StatelessWidget {
drawerItems.add(const Divider()); drawerItems.add(const Divider());
drawerItems.add( drawerItems.add(
ListTile( ListTile(
leading: const Icon(Icons.logout, color: Colors.red), leading: const Icon(Icons.logout, color: Colors.red),
@ -337,3 +338,6 @@ class CustomDrawer extends StatelessWidget {
); );
} }
} }
class HistoryPage {
}

100
lib/Components/app_bar.dart

@ -1,31 +1,117 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:youmazgestion/controller/userController.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title; final String title;
final Widget? subtitle; final Widget? subtitle;
final List<Widget>? actions;
final bool automaticallyImplyLeading;
final Color? backgroundColor;
const CustomAppBar({ final UserController userController = Get.put(UserController());
CustomAppBar({
Key? key, Key? key,
required this.title, required this.title,
this.subtitle, this.subtitle,
this.actions,
this.automaticallyImplyLeading = true,
this.backgroundColor,
}) : super(key: key); }) : super(key: key);
@override @override
Size get preferredSize => Size.fromHeight(subtitle == null ? 56.0 : 72.0); Size get preferredSize => Size.fromHeight(subtitle == null ? 56.0 : 80.0);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBar( return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.blue.shade900,
Colors.blue.shade800,
],
),
boxShadow: [
BoxShadow(
color: Colors.blue.shade900.withOpacity(0.3),
offset: const Offset(0, 2),
blurRadius: 4,
),
],
),
child: AppBar(
backgroundColor: backgroundColor ?? Colors.transparent,
elevation: 0,
automaticallyImplyLeading: automaticallyImplyLeading,
centerTitle: false,
iconTheme: const IconThemeData(
color: Colors.white,
size: 24,
),
actions: actions,
title: subtitle == null title: subtitle == null
? Text(title) ? Text(
title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 0.5,
),
)
: Column( : Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(title, style: TextStyle(fontSize: 20)), Text(
subtitle!, title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: 0.5,
),
),
const SizedBox(height: 2),
Obx(() => Text(
userController.role!='Super Admin'?'Point de vente: ${userController.pointDeVenteDesignation}':'',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.white.withOpacity(0.9),
letterSpacing: 0.3,
),
)),
if (subtitle != null) ...[
const SizedBox(height: 2),
DefaultTextStyle(
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.8),
fontWeight: FontWeight.w400,
),
child: subtitle!,
),
], ],
],
),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.blue.shade900,
Colors.blue.shade800,
],
),
),
),
), ),
// autres propriétés si besoin
); );
} }
} }

12
lib/Models/Client.dart

@ -49,7 +49,6 @@ class Client {
String get nomComplet => '$prenom $nom'; String get nomComplet => '$prenom $nom';
} }
// Models/commande.dart
enum StatutCommande { enum StatutCommande {
enAttente, enAttente,
confirmee, confirmee,
@ -67,6 +66,8 @@ class Commande {
final double montantTotal; final double montantTotal;
final String? notes; final String? notes;
final DateTime? dateLivraison; final DateTime? dateLivraison;
final int? commandeurId;
final int? validateurId;
// Données du client (pour les jointures) // Données du client (pour les jointures)
final String? clientNom; final String? clientNom;
@ -81,6 +82,8 @@ class Commande {
required this.montantTotal, required this.montantTotal,
this.notes, this.notes,
this.dateLivraison, this.dateLivraison,
this.commandeurId,
this.validateurId,
this.clientNom, this.clientNom,
this.clientPrenom, this.clientPrenom,
this.clientEmail, this.clientEmail,
@ -95,6 +98,8 @@ class Commande {
'montantTotal': montantTotal, 'montantTotal': montantTotal,
'notes': notes, 'notes': notes,
'dateLivraison': dateLivraison?.toIso8601String(), 'dateLivraison': dateLivraison?.toIso8601String(),
'commandeurId': commandeurId,
'validateurId': validateurId,
}; };
} }
@ -109,6 +114,8 @@ class Commande {
dateLivraison: map['dateLivraison'] != null dateLivraison: map['dateLivraison'] != null
? DateTime.parse(map['dateLivraison']) ? DateTime.parse(map['dateLivraison'])
: null, : null,
commandeurId: map['commandeurId'],
validateurId: map['validateurId'],
clientNom: map['clientNom'], clientNom: map['clientNom'],
clientPrenom: map['clientPrenom'], clientPrenom: map['clientPrenom'],
clientEmail: map['clientEmail'], clientEmail: map['clientEmail'],
@ -129,6 +136,8 @@ class Commande {
return 'Livrée'; return 'Livrée';
case StatutCommande.annulee: case StatutCommande.annulee:
return 'Annulée'; return 'Annulée';
default:
return 'Inconnu';
} }
} }
@ -138,6 +147,7 @@ class Commande {
: 'Client inconnu'; : 'Client inconnu';
} }
// Models/detail_commande.dart // Models/detail_commande.dart
class DetailCommande { class DetailCommande {
final int? id; final int? id;

4
lib/Models/produit.dart

@ -8,6 +8,7 @@ class Product {
final String? description; final String? description;
String? qrCode; String? qrCode;
final String? reference; final String? reference;
final int? pointDeVenteId;
Product({ Product({
this.id, this.id,
@ -19,6 +20,7 @@ class Product {
this.description = '', this.description = '',
this.qrCode, this.qrCode,
this.reference, this.reference,
this.pointDeVenteId
}); });
// Vérifie si le stock est défini // Vérifie si le stock est défini
bool isStockDefined() { bool isStockDefined() {
@ -40,6 +42,7 @@ class Product {
'description': description ?? '', 'description': description ?? '',
'qrCode': qrCode ?? '', 'qrCode': qrCode ?? '',
'reference': reference ?? '', 'reference': reference ?? '',
'point_de_vente_id':pointDeVenteId
}; };
} }
@ -54,6 +57,7 @@ class Product {
description: map['description'], description: map['description'],
qrCode: map['qrCode'], qrCode: map['qrCode'],
reference: map['reference'], reference: map['reference'],
pointDeVenteId : map['point_de_vente_id']
); );
} }
} }

5
lib/Models/users.dart

@ -7,6 +7,7 @@ class Users {
String username; String username;
int roleId; int roleId;
String? roleName; // Optionnel, rempli lors des requêtes avec JOIN String? roleName; // Optionnel, rempli lors des requêtes avec JOIN
int? pointDeVenteId;
Users({ Users({
this.id, this.id,
@ -17,6 +18,7 @@ class Users {
required this.username, required this.username,
required this.roleId, required this.roleId,
this.roleName, this.roleName,
this.pointDeVenteId,
}); });
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
@ -27,6 +29,7 @@ class Users {
'password': password, 'password': password,
'username': username, 'username': username,
'role_id': roleId, 'role_id': roleId,
'point_de_vente_id' : pointDeVenteId,
}; };
} }
@ -46,9 +49,11 @@ class Users {
username: map['username'], username: map['username'],
roleId: map['role_id'], roleId: map['role_id'],
roleName: map['role_name'], // Depuis les requêtes avec JOIN roleName: map['role_name'], // Depuis les requêtes avec JOIN
pointDeVenteId : map['point_de_vente_id']
); );
} }
// Getter pour la compatibilité avec l'ancien code // Getter pour la compatibilité avec l'ancien code
String get role => roleName ?? ''; String get role => roleName ?? '';
} }

0
lib/Services/GestionStockDatabase.dart

680
lib/Services/app_database.dart

@ -1,680 +0,0 @@
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/users.dart';
import '../Models/role.dart';
import '../Models/Permission.dart';
class AppDatabase {
static final AppDatabase instance = AppDatabase._init();
late Database _database;
AppDatabase._init() {
sqfliteFfiInit();
}
Future<Database> get database async {
if (_database.isOpen) return _database;
_database = await _initDB('app_database.db');
return _database;
}
Future<void> initDatabase() async {
_database = await _initDB('app_database.db');
await _createDB(_database, 1);
await insertDefaultPermissions();
await insertDefaultMenus();
await insertDefaultRoles();
await insertDefaultSuperAdmin();
}
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 d\'une 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();
if (!tableNames.contains('roles')) {
await db.execute('''
CREATE TABLE roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
designation TEXT NOT NULL UNIQUE
)
''');
print("Table 'roles' créée.");
}
if (!tableNames.contains('permissions')) {
await db.execute('''
CREATE TABLE permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
)
''');
print("Table 'permissions' créée.");
}
if (!tableNames.contains('menu')) {
await db.execute('''
CREATE TABLE menu (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
route TEXT NOT NULL UNIQUE
)
''');
print("Table 'menu' créée.");
}
if (!tableNames.contains('role_permissions')) {
await db.execute('''
CREATE TABLE role_permissions (
role_id INTEGER,
permission_id INTEGER,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
)
''');
print("Table 'role_permissions' créée.");
}
if (!tableNames.contains('menu_permissions')) {
await db.execute('''
CREATE TABLE menu_permissions (
menu_id INTEGER,
permission_id INTEGER,
PRIMARY KEY (menu_id, permission_id),
FOREIGN KEY (menu_id) REFERENCES menu(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
)
''');
print("Table 'menu_permissions' créée.");
}
if (!tableNames.contains('users')) {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
lastname TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
username TEXT NOT NULL UNIQUE,
role_id INTEGER NOT NULL,
FOREIGN KEY (role_id) REFERENCES roles(id)
)
''');
print("Table 'users' créée.");
}
if (!tableNames.contains('role_menu_permissions')) {
await db.execute('''
CREATE TABLE role_menu_permissions (
role_id INTEGER,
menu_id INTEGER,
permission_id INTEGER,
PRIMARY KEY (role_id, menu_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
FOREIGN KEY (menu_id) REFERENCES menu(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
)
''');
print("Table 'role_menu_permissions' créée.");
}
}
Future<void> insertDefaultPermissions() async {
final db = await database;
final existing = await db.query('permissions');
if (existing.isEmpty) {
await db.insert('permissions', {'name': 'view'});
await db.insert('permissions', {'name': 'create'});
await db.insert('permissions', {'name': 'update'});
await db.insert('permissions', {'name': 'delete'});
await db.insert('permissions', {'name': 'admin'});
await db.insert('permissions', {'name': 'manage'}); // Nouvelle permission
await db.insert('permissions', {'name': 'read'}); // Nouvelle permission
print("Permissions par défaut insérées");
} else {
// Vérifier et ajouter les nouvelles permissions si elles n'existent pas
final newPermissions = ['manage', 'read'];
for (var permission in newPermissions) {
final existingPermission = await db.query('permissions', where: 'name = ?', whereArgs: [permission]);
if (existingPermission.isEmpty) {
await db.insert('permissions', {'name': permission});
print("Permission ajoutée: $permission");
}
}
}
}
Future<void> insertDefaultMenus() async {
final db = await database;
final existingMenus = await db.query('menu');
if (existingMenus.isEmpty) {
// Menus existants
await db.insert('menu', {'name': 'Accueil', 'route': '/accueil'});
await db.insert('menu', {'name': 'Ajouter un utilisateur', 'route': '/ajouter-utilisateur'});
await db.insert('menu', {'name': 'Modifier/Supprimer un utilisateur', 'route': '/modifier-utilisateur'});
await db.insert('menu', {'name': 'Ajouter un produit', 'route': '/ajouter-produit'});
await db.insert('menu', {'name': 'Modifier/Supprimer un produit', 'route': '/modifier-produit'});
await db.insert('menu', {'name': 'Bilan', 'route': '/bilan'});
await db.insert('menu', {'name': 'Gérer les rôles', 'route': '/gerer-roles'});
await db.insert('menu', {'name': 'Gestion de stock', 'route': '/gestion-stock'});
await db.insert('menu', {'name': 'Historique', 'route': '/historique'});
await db.insert('menu', {'name': 'Déconnexion', 'route': '/deconnexion'});
// Nouveaux menus ajoutés
await db.insert('menu', {'name': 'Nouvelle commande', 'route': '/nouvelle-commande'});
await db.insert('menu', {'name': 'Gérer les commandes', 'route': '/gerer-commandes'});
print("Menus par défaut insérés");
} else {
// Si des menus existent déjà, vérifier et ajouter les nouveaux menus manquants
await _addMissingMenus(db);
}
}
Future<void> _addMissingMenus(Database db) async {
final menusToAdd = [
{'name': 'Nouvelle commande', 'route': '/nouvelle-commande'},
{'name': 'Gérer les commandes', 'route': '/gerer-commandes'},
];
for (var menu in menusToAdd) {
final existing = await db.query(
'menu',
where: 'route = ?',
whereArgs: [menu['route']],
);
if (existing.isEmpty) {
await db.insert('menu', menu);
print("Menu ajouté: ${menu['name']}");
}
}
}
Future<void> insertDefaultRoles() async {
final db = await database;
final existingRoles = await db.query('roles');
if (existingRoles.isEmpty) {
int superAdminRoleId = await db.insert('roles', {'designation': 'Super Admin'});
int adminRoleId = await db.insert('roles', {'designation': 'Admin'});
int userRoleId = await db.insert('roles', {'designation': 'User'});
final permissions = await db.query('permissions');
final menus = await db.query('menu');
// Assigner toutes les permissions à tous les menus pour le Super Admin
for (var menu in menus) {
for (var permission in permissions) {
await db.insert('role_menu_permissions', {
'role_id': superAdminRoleId,
'menu_id': menu['id'],
'permission_id': permission['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
}
}
// Assigner quelques permissions à l'Admin et à l'User pour les nouveaux menus
await _assignBasicPermissionsToRoles(db, adminRoleId, userRoleId);
print("Rôles par défaut créés et permissions assignées");
} else {
// Si les rôles existent déjà, vérifier et ajouter les permissions manquantes
await _updateExistingRolePermissions(db);
}
}
// Nouvelle méthode pour assigner les permissions de base aux nouveaux menus
Future<void> _assignBasicPermissionsToRoles(Database db, int adminRoleId, int userRoleId) async {
final viewPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['view']);
final createPermission = await db.query('permissions', where: 'name = ?', whereArgs: ['create']);
final updatePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['update']);
final managePermission = await db.query('permissions', where: 'name = ?', whereArgs: ['manage']);
// Récupérer les IDs des nouveaux menus
final nouvelleCommandeMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/nouvelle-commande']);
final gererCommandesMenu = await db.query('menu', where: 'route = ?', whereArgs: ['/gerer-commandes']);
if (nouvelleCommandeMenu.isNotEmpty && createPermission.isNotEmpty) {
// Admin peut créer de nouvelles commandes
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
// User peut aussi créer de nouvelles commandes
await db.insert('role_menu_permissions', {
'role_id': userRoleId,
'menu_id': nouvelleCommandeMenu.first['id'],
'permission_id': createPermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
}
if (gererCommandesMenu.isNotEmpty && managePermission.isNotEmpty) {
// Admin peut gérer les commandes
await db.insert('role_menu_permissions', {
'role_id': adminRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': managePermission.first['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
}
if (gererCommandesMenu.isNotEmpty && viewPermission.isNotEmpty) {
// User peut voir les commandes
await db.insert('role_menu_permissions', {
'role_id': userRoleId,
'menu_id': gererCommandesMenu.first['id'],
'permission_id': viewPermission.first['id'],
}
, conflictAlgorithm: ConflictAlgorithm.ignore
);
}
}
Future<void> _updateExistingRolePermissions(Database db) async {
final superAdminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Super Admin']);
if (superAdminRole.isNotEmpty) {
final superAdminRoleId = superAdminRole.first['id'] as int;
final permissions = await db.query('permissions');
final menus = await db.query('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(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [superAdminRoleId, menu['id'], permission['id']],
);
if (existingPermission.isEmpty) {
await db.insert('role_menu_permissions', {
'role_id': superAdminRoleId,
'menu_id': menu['id'],
'permission_id': permission['id'],
},
conflictAlgorithm: ConflictAlgorithm.ignore
);
}
}
}
// Assigner les permissions de base aux autres rôles pour les nouveaux menus
final adminRole = await db.query('roles', where: 'designation = ?', whereArgs: ['Admin']);
final userRole = await db.query('roles', where: 'designation = ?', whereArgs: ['User']);
if (adminRole.isNotEmpty && userRole.isNotEmpty) {
await _assignBasicPermissionsToRoles(db, adminRole.first['id'] as int, userRole.first['id'] as int);
}
print("Permissions mises à jour pour tous les rôles");
}
}
Future<void> insertDefaultSuperAdmin() async {
final db = await database;
final existingSuperAdmin = await db.rawQuery('''
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('roles',
where: 'designation = ?',
whereArgs: ['Super Admin']
);
if (superAdminRole.isNotEmpty) {
final superAdminRoleId = superAdminRole.first['id'] as int;
await db.insert('users', {
'name': 'Super',
'lastname': 'Admin',
'email': 'superadmin@youmazgestion.com',
'password': 'admin123',
'username': 'superadmin',
'role_id': 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à");
}
}
Future<int> createUser(Users user) async {
final db = await database;
return await db.insert('users', user.toMap());
}
Future<int> deleteUser(int id) async {
final db = await database;
return await db.delete('users', where: 'id = ?', whereArgs: [id]);
}
Future<int> updateUser(Users user) async {
final db = await database;
return await db.update('users', user.toMap(), where: 'id = ?', whereArgs: [user.id]);
}
Future<int> getUserCount() async {
final db = await database;
List<Map<String, dynamic>> result = await db.rawQuery('SELECT COUNT(*) as count FROM users');
return result.first['count'] as int;
}
Future<bool> verifyUser(String username, String password) async {
final db = await database;
final result = await db.rawQuery('''
SELECT users.id
FROM users
WHERE users.username = ? AND users.password = ?
''', [username, password]);
return result.isNotEmpty;
}
Future<Users> getUser(String username) async {
final db = await database;
final result = await db.rawQuery('''
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);
} else {
throw Exception('User not found');
}
}
Future<Map<String, dynamic>?> getUserCredentials(String username, String password) async {
final db = await database;
final result = await db.rawQuery('''
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) {
return {
'id': result.first['id'],
'username': result.first['username'] as String,
'role': result.first['role_name'] as String,
'role_id': result.first['role_id'],
};
} else {
return null;
}
}
Future<List<Users>> getAllUsers() async {
final db = await database;
final result = await db.rawQuery('''
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((json) => Users.fromMap(json)).toList();
}
Future<int> createRole(Role role) async {
final db = await database;
return await db.insert('roles', role.toMap());
}
Future<List<Role>> getRoles() async {
final db = await database;
final maps = await db.query('roles', orderBy: 'designation ASC');
return List.generate(maps.length, (i) => Role.fromMap(maps[i]));
}
Future<int> updateRole(Role role) async {
final db = await database;
return await db.update(
'roles',
role.toMap(),
where: 'id = ?',
whereArgs: [role.id],
);
}
Future<int> deleteRole(int? id) async {
final db = await database;
return await db.delete(
'roles',
where: 'id = ?',
whereArgs: [id],
);
}
Future<List<Permission>> getAllPermissions() async {
final db = await database;
final result = await db.query('permissions', orderBy: 'name ASC');
return result.map((e) => Permission.fromMap(e)).toList();
}
Future<List<Permission>> getPermissionsForRole(int roleId) async {
final db = await database;
final result = await db.rawQuery('''
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((map) => Permission.fromMap(map)).toList();
}
Future<List<Permission>> getPermissionsForUser(String username) async {
final db = await database;
final result = await db.rawQuery('''
SELECT DISTINCT p.id, p.name
FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
JOIN roles r ON rp.role_id = r.id
JOIN users u ON u.role_id = r.id
WHERE u.username = ?
ORDER BY p.name ASC
''', [username]);
return result.map((map) => Permission.fromMap(map)).toList();
}
Future<void> assignPermission(int roleId, int permissionId) async {
final db = await database;
await db.insert('role_permissions', {
'role_id': roleId,
'permission_id': permissionId,
}, conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<void> removePermission(int roleId, int permissionId) async {
final db = await database;
await db.delete(
'role_permissions',
where: 'role_id = ? AND permission_id = ?',
whereArgs: [roleId, permissionId],
);
}
Future<void> assignMenuPermission(int menuId, int permissionId) async {
final db = await database;
await db.insert('menu_permissions', {
'menu_id': menuId,
'permission_id': permissionId,
}, conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<void> removeMenuPermission(int menuId, int permissionId) async {
final db = await database;
await db.delete(
'menu_permissions',
where: 'menu_id = ? AND permission_id = ?',
whereArgs: [menuId, permissionId],
);
}
Future<bool> isSuperAdmin(String username) async {
final db = await database;
final result = await db.rawQuery('''
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<void> changePassword(String username, String oldPassword, String newPassword) async {
final db = await database;
final isValidOldPassword = await verifyUser(username, oldPassword);
if (!isValidOldPassword) {
throw Exception('Ancien mot de passe incorrect');
}
await db.update(
'users',
{'password': newPassword},
where: 'username = ?',
whereArgs: [username],
);
}
Future<bool> hasPermission(String username, String permissionName, String menuRoute) async {
final db = await database;
final result = await db.rawQuery('''
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;
}
Future<void> close() async {
if (_database.isOpen) {
await _database.close();
}
}
Future<void> printDatabaseInfo() async {
final db = await database;
print("=== INFORMATIONS DE LA BASE DE DONNÉES ===");
final userCount = await getUserCount();
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("=========================================");
}
Future<List<Permission>> getPermissionsForRoleAndMenu(int roleId, int menuId) async {
final db = await database;
final result = await db.rawQuery('''
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((map) => Permission.fromMap(map)).toList();
}
// Ajoutez cette méthode temporaire pour supprimer la DB corrompue
Future<void> deleteDatabaseFile() async {
final documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, 'app_database.db');
final file = File(path);
if (await file.exists()) {
await file.delete();
print("Base de données utilisateur supprimée");
}
}
Future<void> assignRoleMenuPermission(int roleId, int menuId, int permissionId) async {
final db = await database;
await db.insert('role_menu_permissions', {
'role_id': roleId,
'menu_id': menuId,
'permission_id': permissionId,
}, conflictAlgorithm: ConflictAlgorithm.ignore);
}
Future<void> removeRoleMenuPermission(int roleId, int menuId, int permissionId) async {
final db = await database;
await db.delete(
'role_menu_permissions',
where: 'role_id = ? AND menu_id = ? AND permission_id = ?',
whereArgs: [roleId, menuId, permissionId],
);
}
}

559
lib/Services/productDatabase.dart

@ -1,559 +0,0 @@
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");
}
}
}

1139
lib/Services/stock_managementDatabase.dart

File diff suppressed because it is too large

9
lib/Views/HandleProduct.dart

@ -10,10 +10,11 @@ import 'package:qr_flutter/qr_flutter.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:excel/excel.dart' hide Border; import 'package:excel/excel.dart' hide Border;
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import '../Components/appDrawer.dart'; import '../Components/appDrawer.dart';
import '../Components/app_bar.dart'; import '../Components/app_bar.dart';
import '../Models/produit.dart'; import '../Models/produit.dart';
import '../Services/productDatabase.dart'; //import '../Services/productDatabase.dart';
class ProductManagementPage extends StatefulWidget { class ProductManagementPage extends StatefulWidget {
@ -24,7 +25,7 @@ class ProductManagementPage extends StatefulWidget {
} }
class _ProductManagementPageState extends State<ProductManagementPage> { class _ProductManagementPageState extends State<ProductManagementPage> {
final ProductDatabase _productDatabase = ProductDatabase.instance; final AppDatabase _productDatabase = AppDatabase.instance;
List<Product> _products = []; List<Product> _products = [];
List<Product> _filteredProducts = []; List<Product> _filteredProducts = [];
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
@ -1041,7 +1042,7 @@ Future<void> _generatePDF(Product product, String qrUrl) async {
return pw.Center( return pw.Center(
child: pw.Column( child: pw.Column(
children: [ children: [
pw.Text('QR Code - ${product.name}', style: pw.TextStyle(fontSize: 20)), // pw.Text('QR Code - ${product.name}', style: pw.TextStyle(fontSize: 20)),
pw.SizedBox(height: 20), pw.SizedBox(height: 20),
pw.BarcodeWidget( pw.BarcodeWidget(
barcode: pw.Barcode.qrCode(), barcode: pw.Barcode.qrCode(),
@ -1411,7 +1412,7 @@ Future<void> _generatePDF(Product product, String qrUrl) async {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Gestion des produits'), appBar: CustomAppBar(title: 'Gestion des produits'),
drawer: CustomDrawer(), drawer: CustomDrawer(),
floatingActionButton: Column( floatingActionButton: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

7
lib/Views/RoleListPage.dart

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Models/Permission.dart'; //import 'package:youmazgestion/Models/Permission.dart';
import 'package:youmazgestion/Services/app_database.dart'; //import 'package:youmazgestion/Services/app_database.dart';
import 'package:youmazgestion/Models/role.dart'; import 'package:youmazgestion/Models/role.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/Views/RolePermissionPage.dart'; import 'package:youmazgestion/Views/RolePermissionPage.dart';
class RoleListPage extends StatefulWidget { class RoleListPage extends StatefulWidget {
@ -47,7 +48,7 @@ class _RoleListPageState extends State<RoleListPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: "Gestion des rôles"), appBar: CustomAppBar(title: "Gestion des rôles"),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(

3
lib/Views/RolePermissionPage.dart

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Models/Permission.dart'; import 'package:youmazgestion/Models/Permission.dart';
import 'package:youmazgestion/Services/app_database.dart';
import 'package:youmazgestion/Models/role.dart'; import 'package:youmazgestion/Models/role.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
class RolePermissionsPage extends StatefulWidget { class RolePermissionsPage extends StatefulWidget {
final Role role; final Role role;

2
lib/Views/bilanMois.dart

@ -29,7 +29,7 @@ class _BilanMoisState extends State<BilanMois> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Bilan du mois'), appBar: CustomAppBar(title: 'Bilan du mois'),
body: Column( body: Column(
children: [ children: [
// Les 3 cartes en haut // Les 3 cartes en haut

562
lib/Views/commandManagement.dart

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -11,7 +12,8 @@ import 'package:open_file/open_file.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Components/appDrawer.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
import 'package:youmazgestion/Services/productDatabase.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/controller/userController.dart';
class GestionCommandesPage extends StatefulWidget { class GestionCommandesPage extends StatefulWidget {
const GestionCommandesPage({super.key}); const GestionCommandesPage({super.key});
@ -21,13 +23,14 @@ class GestionCommandesPage extends StatefulWidget {
} }
class _GestionCommandesPageState extends State<GestionCommandesPage> { class _GestionCommandesPageState extends State<GestionCommandesPage> {
final ProductDatabase _database = ProductDatabase.instance; final AppDatabase _database = AppDatabase.instance;
List<Commande> _commandes = []; List<Commande> _commandes = [];
List<Commande> _filteredCommandes = []; List<Commande> _filteredCommandes = [];
StatutCommande? _selectedStatut; StatutCommande? _selectedStatut;
DateTime? _selectedDate; DateTime? _selectedDate;
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
bool _showCancelledOrders = false; // Nouveau: contrôle l'affichage des commandes annulées bool _showCancelledOrders = false;
final userController = Get.find<UserController>();
@override @override
void initState() { void initState() {
@ -43,6 +46,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
_filterCommandes(); _filterCommandes();
}); });
} }
Future<Uint8List> loadImage() async { Future<Uint8List> loadImage() async {
final data = await rootBundle.load('assets/youmaz2.png'); final data = await rootBundle.load('assets/youmaz2.png');
return data.buffer.asUint8List(); return data.buffer.asUint8List();
@ -59,7 +63,6 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
DateFormat('yyyy-MM-dd').format(commande.dateCommande) == DateFormat('yyyy-MM-dd').format(commande.dateCommande) ==
DateFormat('yyyy-MM-dd').format(_selectedDate!); DateFormat('yyyy-MM-dd').format(_selectedDate!);
// Nouveau: filtrer les commandes annulées selon le toggle
final shouldShowCancelled = _showCancelledOrders || commande.statut != StatutCommande.annulee; final shouldShowCancelled = _showCancelledOrders || commande.statut != StatutCommande.annulee;
return matchesSearch && matchesStatut && matchesDate && shouldShowCancelled; return matchesSearch && matchesStatut && matchesDate && shouldShowCancelled;
@ -67,11 +70,44 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
}); });
} }
Future<void> _updateStatut(int commandeId, StatutCommande newStatut) async { Future<void> _updateStatut(int commandeId, StatutCommande newStatut, {int? validateurId}) async {
// D'abord récupérer la commande existante pour avoir toutes ses valeurs
final commandeExistante = await _database.getCommandeById(commandeId);
if (commandeExistante == null) {
Get.snackbar(
'Erreur',
'Commande introuvable',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
if (validateurId != null) {
// Mise à jour avec validateur
await _database.updateCommande(Commande(
id: commandeId,
clientId: commandeExistante.clientId,
dateCommande: commandeExistante.dateCommande,
statut: newStatut,
montantTotal: commandeExistante.montantTotal,
notes: commandeExistante.notes,
dateLivraison: commandeExistante.dateLivraison,
commandeurId: commandeExistante.commandeurId,
validateurId: validateurId, // On met à jour le validateur
clientNom: commandeExistante.clientNom,
clientPrenom: commandeExistante.clientPrenom,
clientEmail: commandeExistante.clientEmail,
));
} else {
// Mise à jour simple du statut
await _database.updateStatutCommande(commandeId, newStatut); await _database.updateStatutCommande(commandeId, newStatut);
}
await _loadCommandes(); await _loadCommandes();
// Amélioration: message plus spécifique selon le statut
String message = 'Statut de la commande mis à jour'; String message = 'Statut de la commande mis à jour';
Color backgroundColor = Colors.green; Color backgroundColor = Colors.green;
@ -102,15 +138,114 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
); );
} }
Future<void> _showPaymentOptions(Commande commande) async {
final selectedPayment = await showDialog<PaymentMethod>(
context: context,
builder: (context) => PaymentMethodDialog(commande: commande),
);
if (selectedPayment != null) {
if (selectedPayment.type == PaymentType.cash) {
await _showCashPaymentDialog(commande, selectedPayment.amountGiven);
}
// Confirmer la commande avec le validateur actuel
await _updateStatut(
commande.id!,
StatutCommande.confirmee,
validateurId: userController.userId,
);
// Générer le ticket de caisse
await _generateReceipt(commande, selectedPayment);
}
}
Future<void> _showCashPaymentDialog(Commande commande, double amountGiven) async {
final amountController = TextEditingController(
text: amountGiven.toStringAsFixed(2),
);
await showDialog(
context: context,
builder: (context) {
final change = amountGiven - commande.montantTotal;
return AlertDialog(
title: const Text('Paiement en liquide'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Montant total: ${commande.montantTotal.toStringAsFixed(2)} MGA'),
const SizedBox(height: 10),
TextField(
controller: amountController,
decoration: const InputDecoration(
labelText: 'Montant donné',
prefixText: 'MGA ',
),
keyboardType: TextInputType.number,
onChanged: (value) {
final newAmount = double.tryParse(value) ?? 0;
if (newAmount >= commande.montantTotal) {
setState(() {});
}
},
),
const SizedBox(height: 20),
if (amountGiven >= commande.montantTotal)
Text(
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
if (amountGiven < commande.montantTotal)
Text(
'Montant insuffisant',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.red.shade700,
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Valider'),
),
],
);
},
);
}
Future<void> _generateInvoice(Commande commande) async { Future<void> _generateInvoice(Commande commande) async {
final details = await _database.getDetailsCommande(commande.id!); final details = await _database.getDetailsCommande(commande.id!);
final client = await _database.getClientById(commande.clientId); final client = await _database.getClientById(commande.clientId);
final commandeur = commande.commandeurId != null
? await _database.getUserById(commande.commandeurId!)
: null;
final validateur = commande.validateurId != null
? await _database.getUserById(commande.validateurId!)
: null;
final pointDeVente = commandeur?.pointDeVenteId != null
? await _database.getPointDeVenteById(commandeur!.pointDeVenteId!)
: null;
final pdf = pw.Document(); final pdf = pw.Document();
final imageBytes = await loadImage(); // Charge les données de l'image final imageBytes = await loadImage();
final image = pw.MemoryImage(imageBytes); final image = pw.MemoryImage(imageBytes);
// Amélioration: styles plus professionnels
final headerStyle = pw.TextStyle( final headerStyle = pw.TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
@ -127,7 +262,6 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
color: PdfColors.grey600, color: PdfColors.grey600,
); );
// Contenu du PDF amélioré
pdf.addPage( pdf.addPage(
pw.Page( pw.Page(
margin: const pw.EdgeInsets.all(20), margin: const pw.EdgeInsets.all(20),
@ -135,7 +269,6 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
return pw.Column( return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
// En-tête avec logo (si disponible)
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
@ -143,7 +276,6 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
pw.Column( pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
// Placeholder pour le logo - à remplacer par votre logo
pw.Container( pw.Container(
width: 100, width: 100,
height: 80, height: 80,
@ -157,10 +289,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Text('guycom', style: headerStyle), pw.Text('guycom', style: headerStyle),
pw.Text('123 Rue des Entreprises', style: subtitleStyle), if (pointDeVente != null)
pw.Text('Antananarivo, Madagascar', style: subtitleStyle), pw.Text('Point de vente: ${pointDeVente['designation']}', style: subtitleStyle),
pw.Text('Tél: +213 123 456 789', style: subtitleStyle), pw.Text('Tél: +213 123 456 789', style: subtitleStyle),
pw.Text('Site: guycom.mg', style: subtitleStyle),
], ],
), ),
pw.Column( pw.Column(
@ -217,7 +348,33 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
), ),
pw.SizedBox(height: 30), pw.SizedBox(height: 20),
// Informations personnel
if (commandeur != null || validateur != null)
pw.Container(
width: double.infinity,
padding: const pw.EdgeInsets.all(12),
decoration: pw.BoxDecoration(
color: PdfColors.grey100,
borderRadius: pw.BorderRadius.circular(8),
),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text('PERSONNEL:', style: titleStyle),
pw.SizedBox(height: 5),
if (commandeur != null)
pw.Text('Commandeur: ${commandeur.name} ',
style: pw.TextStyle(fontSize: 12)),
if (validateur != null)
pw.Text('Validateur: ${validateur.name}',
style: pw.TextStyle(fontSize: 12)),
],
),
),
pw.SizedBox(height: 20),
// Tableau des produits // Tableau des produits
pw.Text('DÉTAILS DE LA COMMANDE', style: titleStyle), pw.Text('DÉTAILS DE LA COMMANDE', style: titleStyle),
@ -247,8 +404,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
children: [ children: [
_buildTableCell(detail.produitNom ?? 'Produit inconnu'), _buildTableCell(detail.produitNom ?? 'Produit inconnu'),
_buildTableCell(detail.quantite.toString()), _buildTableCell(detail.quantite.toString()),
_buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} DA'), _buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} MGA'),
_buildTableCell('${detail.sousTotal.toStringAsFixed(2)} DA'), _buildTableCell('${detail.sousTotal.toStringAsFixed(2)} MGA'),
], ],
); );
}), }),
@ -267,7 +424,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
borderRadius: pw.BorderRadius.circular(8), borderRadius: pw.BorderRadius.circular(8),
), ),
child: pw.Text( child: pw.Text(
'TOTAL: ${commande.montantTotal.toStringAsFixed(2)} DA', 'TOTAL: ${commande.montantTotal.toStringAsFixed(2)} MGA',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
@ -312,19 +469,149 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
); );
// Sauvegarder le PDF
final output = await getTemporaryDirectory(); final output = await getTemporaryDirectory();
final file = File('${output.path}/facture_${commande.id}.pdf'); final file = File('${output.path}/facture_${commande.id}.pdf');
await file.writeAsBytes(await pdf.save()); await file.writeAsBytes(await pdf.save());
await OpenFile.open(file.path);
}
Future<void> _generateReceipt(Commande commande, PaymentMethod payment) async {
final details = await _database.getDetailsCommande(commande.id!);
final client = await _database.getClientById(commande.clientId);
final commandeur = commande.commandeurId != null
? await _database.getUserById(commande.commandeurId!)
: null;
final validateur = commande.validateurId != null
? await _database.getUserById(commande.validateurId!)
: null;
final pointDeVente = commandeur?.pointDeVenteId != null
? await _database.getPointDeVenteById(commandeur!.pointDeVenteId!)
: null;
final pdf = pw.Document();
final imageBytes = await loadImage();
final image = pw.MemoryImage(imageBytes);
pdf.addPage(
pw.Page(
pageFormat: PdfPageFormat(70 * PdfPageFormat.mm, double.infinity),
margin: const pw.EdgeInsets.all(4),
build: (pw.Context context) {
return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// En-tête
pw.Center(
child: pw.Container(
width: 50,
height: 50,
child: pw.Image(image),
),
),
pw.SizedBox(height: 4),
pw.Text('TICKET DE CAISSE',
style: pw.TextStyle(
fontSize: 10,
fontWeight: pw.FontWeight.bold,
),
),
pw.Text('N°: ${commande.id}',
style: const pw.TextStyle(fontSize: 8)),
pw.Text('Date: ${DateFormat('dd/MM/yyyy HH:mm').format(commande.dateCommande)}',
style: const pw.TextStyle(fontSize: 8)),
if (pointDeVente != null)
pw.Text('Point de vente: ${pointDeVente['designation']}',
style: const pw.TextStyle(fontSize: 8)),
pw.Divider(thickness: 0.5),
// Client
pw.Text('CLIENT: ${client?.nomComplet ?? 'Non spécifié'}',
style: pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold)),
// Personnel
if (commandeur != null)
pw.Text('Commandeur: ${commandeur.name} ',
style: const pw.TextStyle(fontSize: 7)),
if (validateur != null)
pw.Text('Validateur: ${validateur.name}',
style: const pw.TextStyle(fontSize: 7)),
pw.Divider(thickness: 0.5),
// Détails
pw.Table(
columnWidths: {
0: const pw.FlexColumnWidth(3),
1: const pw.FlexColumnWidth(1),
2: const pw.FlexColumnWidth(2),
},
children: [
pw.TableRow(
children: [
pw.Text('Produit', style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)),
pw.Text('Qté', style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)),
pw.Text('Total', style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)),
],
),
...details.map((detail) => pw.TableRow(
children: [
pw.Text(detail.produitNom ?? 'Produit', style: const pw.TextStyle(fontSize: 7)),
pw.Text(detail.quantite.toString(), style: const pw.TextStyle(fontSize: 7)),
pw.Text('${detail.sousTotal.toStringAsFixed(2)} MGA', style: const pw.TextStyle(fontSize: 7)),
],
)),
],
),
pw.Divider(thickness: 0.5),
// Total
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text('TOTAL:', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)),
pw.Text('${commande.montantTotal.toStringAsFixed(2)} MGA',
style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)),
],
),
// Paiement
pw.SizedBox(height: 8),
pw.Text('MODE DE PAIEMENT:', style: const pw.TextStyle(fontSize: 8)),
pw.Text(
payment.type == PaymentType.cash
? 'LIQUIDE (${payment.amountGiven.toStringAsFixed(2)} MGA)'
: 'CARTE BANCAIRE',
style: pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold),
),
if (payment.type == PaymentType.cash && payment.amountGiven > commande.montantTotal)
pw.Text('Monnaie rendue: ${(payment.amountGiven - commande.montantTotal).toStringAsFixed(2)} MGA',
style: const pw.TextStyle(fontSize: 8)),
pw.SizedBox(height: 12),
pw.Text('Merci pour votre achat !',
style: pw.TextStyle(fontSize: 8, fontStyle: pw.FontStyle.italic)),
pw.Text('www.guycom.mg',
style: const pw.TextStyle(fontSize: 7)),
],
);
},
),
);
// Ouvrir le PDF final output = await getTemporaryDirectory();
final file = File('${output.path}/ticket_${commande.id}.pdf');
await file.writeAsBytes(await pdf.save());
await OpenFile.open(file.path); await OpenFile.open(file.path);
} }
pw.Widget _buildTableCell(String text, [pw.TextStyle? style]) { pw.Widget _buildTableCell(String text, [pw.TextStyle? style]) {
return pw.Padding( return pw.Padding(
padding: const pw.EdgeInsets.all(8.0), padding: const pw.EdgeInsets.all(4.0),
child: pw.Text(text, style: style ?? pw.TextStyle(fontSize: 10)), child: pw.Text(text, style: style ?? pw.TextStyle(fontSize: 8)),
); );
} }
@ -365,7 +652,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Gestion des Commandes'), appBar: CustomAppBar(title: 'Gestion des Commandes'),
drawer: CustomDrawer(), drawer: CustomDrawer(),
body: Column( body: Column(
children: [ children: [
@ -394,14 +681,13 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withOpacity(0.1),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),)
),
], ],
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Image.asset( child: Image.asset(
'assets/logo.png', // Remplacez par le chemin de votre logo 'assets/logo.png',
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return Container( return Container(
@ -456,8 +742,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withOpacity(0.1),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),)
),
], ],
), ),
child: TextField( child: TextField(
@ -643,8 +928,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withOpacity(0.1),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),)
),
], ],
), ),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
@ -829,7 +1113,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'${commande.montantTotal.toStringAsFixed(2)} DA', '${commande.montantTotal.toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -884,6 +1168,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
_CommandeActions( _CommandeActions(
commande: commande, commande: commande,
onStatutChanged: _updateStatut, onStatutChanged: _updateStatut,
onPaymentSelected: _showPaymentOptions,
), ),
], ],
), ),
@ -931,7 +1216,7 @@ class _CommandeDetails extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder<List<DetailCommande>>( return FutureBuilder<List<DetailCommande>>(
future: ProductDatabase.instance.getDetailsCommande(commande.id!), future: AppDatabase.instance.getDetailsCommande(commande.id!),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
@ -984,8 +1269,8 @@ class _CommandeDetails extends StatelessWidget {
children: [ children: [
_buildTableCell(detail.produitNom ?? 'Produit inconnu'), _buildTableCell(detail.produitNom ?? 'Produit inconnu'),
_buildTableCell('${detail.quantite}'), _buildTableCell('${detail.quantite}'),
_buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} DA'), _buildTableCell('${detail.prixUnitaire.toStringAsFixed(2)} MGA'),
_buildTableCell('${detail.sousTotal.toStringAsFixed(2)} DA'), _buildTableCell('${detail.sousTotal.toStringAsFixed(2)} MGA'),
], ],
)), )),
], ],
@ -1010,7 +1295,7 @@ class _CommandeDetails extends StatelessWidget {
), ),
), ),
Text( Text(
'${commande.montantTotal.toStringAsFixed(2)} DA', '${commande.montantTotal.toStringAsFixed(2)} MGA',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 18, fontSize: 18,
@ -1055,10 +1340,12 @@ class _CommandeDetails extends StatelessWidget {
class _CommandeActions extends StatelessWidget { class _CommandeActions extends StatelessWidget {
final Commande commande; final Commande commande;
final Function(int, StatutCommande) onStatutChanged; final Function(int, StatutCommande) onStatutChanged;
final Function(Commande) onPaymentSelected;
const _CommandeActions({ const _CommandeActions({
required this.commande, required this.commande,
required this.onStatutChanged, required this.onStatutChanged,
required this.onPaymentSelected,
}); });
@override @override
@ -1101,12 +1388,7 @@ class _CommandeActions extends StatelessWidget {
label: 'Confirmer', label: 'Confirmer',
icon: Icons.check_circle, icon: Icons.check_circle,
color: Colors.blue, color: Colors.blue,
onPressed: () => _showConfirmDialog( onPressed: () => onPaymentSelected(commande),
context,
'Confirmer la commande',
'Êtes-vous sûr de vouloir confirmer cette commande?',
() => onStatutChanged(commande.id!, StatutCommande.confirmee),
),
), ),
_buildActionButton( _buildActionButton(
label: 'Annuler', label: 'Annuler',
@ -1123,75 +1405,8 @@ class _CommandeActions extends StatelessWidget {
break; break;
case StatutCommande.confirmee: case StatutCommande.confirmee:
buttons.addAll([
_buildActionButton(
label: 'Préparer',
icon: Icons.settings,
color: Colors.amber,
onPressed: () => _showConfirmDialog(
context,
'Marquer en préparation',
'La commande va être marquée comme étant en cours de préparation.',
() => onStatutChanged(commande.id!, StatutCommande.enPreparation),
),
),
_buildActionButton(
label: 'Annuler',
icon: Icons.cancel,
color: Colors.red,
onPressed: () => _showConfirmDialog(
context,
'Annuler la commande',
'Êtes-vous sûr de vouloir annuler cette commande?',
() => onStatutChanged(commande.id!, StatutCommande.annulee),
),
),
]);
break;
case StatutCommande.enPreparation: case StatutCommande.enPreparation:
buttons.addAll([
_buildActionButton(
label: 'Expédier',
icon: Icons.local_shipping,
color: Colors.purple,
onPressed: () => _showConfirmDialog(
context,
'Expédier la commande',
'La commande va être marquée comme expédiée.',
() => onStatutChanged(commande.id!, StatutCommande.expediee),
),
),
_buildActionButton(
label: 'Annuler',
icon: Icons.cancel,
color: Colors.red,
onPressed: () => _showConfirmDialog(
context,
'Annuler la commande',
'Êtes-vous sûr de vouloir annuler cette commande?',
() => onStatutChanged(commande.id!, StatutCommande.annulee),
),
),
]);
break;
case StatutCommande.expediee: case StatutCommande.expediee:
buttons.add(
_buildActionButton(
label: 'Marquer livrée',
icon: Icons.check_circle,
color: Colors.green,
onPressed: () => _showConfirmDialog(
context,
'Marquer comme livrée',
'La commande va être marquée comme livrée.',
() => onStatutChanged(commande.id!, StatutCommande.livree),
),
),
);
break;
case StatutCommande.livree: case StatutCommande.livree:
buttons.add( buttons.add(
Container( Container(
@ -1207,7 +1422,7 @@ class _CommandeActions extends StatelessWidget {
Icon(Icons.check_circle, color: Colors.green.shade600, size: 16), Icon(Icons.check_circle, color: Colors.green.shade600, size: 16),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Commande livrée', 'Commande confirmée',
style: TextStyle( style: TextStyle(
color: Colors.green.shade700, color: Colors.green.shade700,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -1327,3 +1542,130 @@ class _CommandeActions extends StatelessWidget {
); );
} }
} }
enum PaymentType { cash, card }
class PaymentMethod {
final PaymentType type;
final double amountGiven;
PaymentMethod({required this.type, this.amountGiven = 0});
}
class PaymentMethodDialog extends StatefulWidget {
final Commande commande;
const PaymentMethodDialog({super.key, required this.commande});
@override
_PaymentMethodDialogState createState() => _PaymentMethodDialogState();
}
class _PaymentMethodDialogState extends State<PaymentMethodDialog> {
PaymentType _selectedPayment = PaymentType.cash;
final _amountController = TextEditingController();
@override
void initState() {
super.initState();
// Initialiser avec le montant total de la commande
_amountController.text = widget.commande.montantTotal.toStringAsFixed(2);
}
@override
void dispose() {
_amountController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final amount = double.tryParse(_amountController.text) ?? 0;
final change = amount - widget.commande.montantTotal;
return AlertDialog(
title: const Text('Méthode de paiement'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<PaymentType>(
title: const Text('Paiement en liquide'),
value: PaymentType.cash,
groupValue: _selectedPayment,
onChanged: (value) {
setState(() {
_selectedPayment = value!;
});
},
),
if (_selectedPayment == PaymentType.cash)
TextField(
controller: _amountController,
decoration: const InputDecoration(
labelText: 'Montant donné',
prefixText: 'MGA ',
),
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {}); // Rafraîchir l'UI pour calculer la monnaie
},
),
RadioListTile<PaymentType>(
title: const Text('Carte bancaire'),
value: PaymentType.card,
groupValue: _selectedPayment,
onChanged: (value) {
setState(() {
_selectedPayment = value!;
});
},
),
if (_selectedPayment == PaymentType.cash)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: change >= 0 ? Colors.green : Colors.red,
),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () {
final amount = _selectedPayment == PaymentType.cash
? double.tryParse(_amountController.text) ?? 0
: widget.commande.montantTotal;
if (_selectedPayment == PaymentType.cash && amount < widget.commande.montantTotal) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Le montant donné est insuffisant'),
backgroundColor: Colors.red,
),
);
return;
}
Navigator.pop(
context,
PaymentMethod(
type: _selectedPayment,
amountGiven: amount,
),
);
},
child: const Text('Confirmer'),
),
],
);
}
}

5
lib/Views/editProduct.dart

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import '../Models/produit.dart'; import '../Models/produit.dart';
import '../Services/productDatabase.dart'; //import '../Services/productDatabase.dart';
import 'gestionProduct.dart'; import 'gestionProduct.dart';
class EditProductPage extends StatelessWidget { class EditProductPage extends StatelessWidget {
@ -31,7 +32,7 @@ class EditProductPage extends StatelessWidget {
category: category, category: category,
); );
await ProductDatabase.instance.updateProduct(updatedProduct); await AppDatabase.instance.updateProduct(updatedProduct);
Get.to(GestionProduit()); Get.to(GestionProduit());
} else { } else {

3
lib/Views/editUser.dart

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Models/role.dart'; import 'package:youmazgestion/Models/role.dart';
import '../Services/app_database.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
//import '../Services/app_database.dart';
class EditUserPage extends StatefulWidget { class EditUserPage extends StatefulWidget {
final Users user; final Users user;

7
lib/Views/gestionProduct.dart

@ -1,14 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import '../Components/appDrawer.dart'; import '../Components/appDrawer.dart';
import '../Models/produit.dart'; import '../Models/produit.dart';
import '../Services/productDatabase.dart'; // import '../Services/productDatabase.dart';
import 'editProduct.dart'; import 'editProduct.dart';
import 'dart:io'; import 'dart:io';
class GestionProduit extends StatelessWidget { class GestionProduit extends StatelessWidget {
final ProductDatabase _productDatabase = ProductDatabase.instance; final AppDatabase _productDatabase = AppDatabase.instance;
GestionProduit({super.key}); GestionProduit({super.key});
@ -17,7 +18,7 @@ class GestionProduit extends StatelessWidget {
final screenWidth = MediaQuery.of(context).size.width * 0.8; final screenWidth = MediaQuery.of(context).size.width * 0.8;
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Gestion des produits'), appBar: CustomAppBar(title: 'Gestion des produits'),
drawer: CustomDrawer(), drawer: CustomDrawer(),
body: FutureBuilder<List<Product>>( body: FutureBuilder<List<Product>>(
future: _productDatabase.getProducts(), future: _productDatabase.getProducts(),

5
lib/Views/gestionRole.dart

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Models/Permission.dart'; import 'package:youmazgestion/Models/Permission.dart';
import 'package:youmazgestion/Services/app_database.dart'; //import 'package:youmazgestion/Services/app_database.dart';
import 'package:youmazgestion/Models/role.dart'; import 'package:youmazgestion/Models/role.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
class HandleUserRole extends StatefulWidget { class HandleUserRole extends StatefulWidget {
const HandleUserRole({super.key}); const HandleUserRole({super.key});
@ -83,7 +84,7 @@ class _HandleUserRoleState extends State<HandleUserRole> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: "Gestion des rôles"), appBar: CustomAppBar(title: "Gestion des rôles"),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(

7
lib/Views/gestionStock.dart

@ -2,9 +2,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_core/src/get_main.dart';
import 'package:youmazgestion/Models/produit.dart'; import 'package:youmazgestion/Models/produit.dart';
import 'package:youmazgestion/Services/productDatabase.dart'; //import 'package:youmazgestion/Services/productDatabase.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Components/appDrawer.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
class GestionStockPage extends StatefulWidget { class GestionStockPage extends StatefulWidget {
const GestionStockPage({super.key}); const GestionStockPage({super.key});
@ -14,7 +15,7 @@ class GestionStockPage extends StatefulWidget {
} }
class _GestionStockPageState extends State<GestionStockPage> { class _GestionStockPageState extends State<GestionStockPage> {
final ProductDatabase _database = ProductDatabase.instance; final AppDatabase _database = AppDatabase.instance;
List<Product> _products = []; List<Product> _products = [];
List<Product> _filteredProducts = []; List<Product> _filteredProducts = [];
String? _selectedCategory; String? _selectedCategory;
@ -79,7 +80,7 @@ class _GestionStockPageState extends State<GestionStockPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Gestion des Stocks'), appBar: CustomAppBar(title: 'Gestion des Stocks'),
drawer: CustomDrawer(), drawer: CustomDrawer(),
body: Column( body: Column(
children: [ children: [

438
lib/Views/historique.dart

@ -1,123 +1,403 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Components/app_bar.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../Components/appDrawer.dart'; import 'package:youmazgestion/Models/client.dart';
import '../controller/HistoryController.dart'; import 'package:youmazgestion/Models/produit.dart';
import 'listCommandeHistory.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
class HistoriquePage extends StatefulWidget {
const HistoriquePage({super.key});
class HistoryPage extends GetView<HistoryController> {
@override @override
HistoryController controller = Get.put(HistoryController()); _HistoriquePageState createState() => _HistoriquePageState();
}
HistoryPage({super.key}); class _HistoriquePageState extends State<HistoriquePage> {
final AppDatabase _appDatabase = AppDatabase.instance;
List<Commande> _commandes = [];
bool _isLoading = true;
DateTimeRange? _dateRange;
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_loadCommandes();
}
Future<void> _loadCommandes() async {
setState(() {
_isLoading = true;
});
try {
final commandes = await _appDatabase.getCommandes();
setState(() {
_commandes = commandes;
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
Get.snackbar(
'Erreur',
'Impossible de charger les commandes: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
Future<void> _selectDateRange(BuildContext context) async {
final DateTimeRange? picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2020),
lastDate: DateTime.now().add(const Duration(days: 365)),
initialDateRange: _dateRange ?? DateTimeRange(
start: DateTime.now().subtract(const Duration(days: 30)),
end: DateTime.now(),
),
);
if (picked != null) {
setState(() {
_dateRange = picked;
});
_filterCommandes();
}
}
void _filterCommandes() {
final searchText = _searchController.text.toLowerCase();
setState(() {
_isLoading = true;
});
_appDatabase.getCommandes().then((commandes) {
List<Commande> filtered = commandes;
// Filtre par date
if (_dateRange != null) {
filtered = filtered.where((commande) {
final date = commande.dateCommande;
return date.isAfter(_dateRange!.start) &&
date.isBefore(_dateRange!.end.add(const Duration(days: 1)));
}).toList();
}
// Filtre par recherche
if (searchText.isNotEmpty) {
filtered = filtered.where((commande) {
return commande.clientNom!.toLowerCase().contains(searchText) ||
commande.clientPrenom!.toLowerCase().contains(searchText) ||
commande.id.toString().contains(searchText);
}).toList();
}
setState(() {
_commandes = filtered;
_isLoading = false;
});
});
}
void _showCommandeDetails(Commande commande) async {
final details = await _appDatabase.getDetailsCommande(commande.id!);
final client = await _appDatabase.getClientById(commande.clientId);
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(16),
height: MediaQuery.of(context).size.height * 0.7,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Commande #${commande.id}',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Get.back(),
),
],
),
const Divider(),
Text(
'Client: ${client?.nom} ${client?.prenom}',
style: const TextStyle(fontSize: 16),
),
Text(
'Date: ${commande.dateCommande}',
style: const TextStyle(fontSize: 16),
),
Text(
'Statut: ${_getStatutText(commande.statut)}',
style: TextStyle(
fontSize: 16,
color: _getStatutColor(commande.statut),
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
const Text(
'Articles:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Expanded(
child: ListView.builder(
itemCount: details.length,
itemBuilder: (context, index) {
final detail = details[index];
return ListTile(
leading: const Icon(Icons.shopping_bag),
title: Text(detail.produitNom ?? 'Produit inconnu'),
subtitle: Text(
'${detail.quantite} x ${detail.prixUnitaire.toStringAsFixed(2)} DA',
),
trailing: Text(
'${detail.sousTotal.toStringAsFixed(2)} DA',
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
},
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Total:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
'${commande.montantTotal.toStringAsFixed(2)} DA',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
),
if (commande.statut == StatutCommande.enAttente ||
commande.statut == StatutCommande.enPreparation)
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
onPressed: () => _updateStatutCommande(commande.id!),
child: const Text('Marquer comme livrée'),
),
],
),
),
);
}
String _getStatutText(StatutCommande statut) {
switch (statut) {
case StatutCommande.enAttente:
return 'En attente';
case StatutCommande.confirmee:
return 'Confirmée';
case StatutCommande.enPreparation:
return 'En préparation';
case StatutCommande.livree:
return 'Livrée';
case StatutCommande.annulee:
return 'Annulée';
default:
return 'Inconnu';
}
}
Color _getStatutColor(StatutCommande statut) {
switch (statut) {
case StatutCommande.enAttente:
return Colors.orange;
case StatutCommande.confirmee:
return Colors.blue;
case StatutCommande.enPreparation:
return Colors.purple;
case StatutCommande.livree:
return Colors.green;
case StatutCommande.annulee:
return Colors.red;
default:
return Colors.grey;
}
}
Future<void> _updateStatutCommande(int commandeId) async {
try {
await _appDatabase.updateStatutCommande(
commandeId, StatutCommande.livree);
Get.back(); // Ferme le bottom sheet
_loadCommandes();
Get.snackbar(
'Succès',
'Statut de la commande mis à jour',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
);
} catch (e) {
Get.snackbar(
'Erreur',
'Impossible de mettre à jour le statut: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Historique'), appBar: AppBar(
drawer: CustomDrawer(), title: const Text('Historique des Commandes'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadCommandes,
),
],
),
body: Column( body: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(8),
child: ElevatedButton( child: Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
decoration: InputDecoration(
labelText: 'Rechercher',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () { onPressed: () {
controller.refreshOrders(); _searchController.clear();
controller.onInit(); _filterCommandes();
}, },
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepOrangeAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
), ),
), ),
child: const Text( onChanged: (value) => _filterCommandes(),
'Rafraîchir',
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
), ),
), ),
IconButton(
icon: const Icon(Icons.date_range),
onPressed: () => _selectDateRange(context),
), ),
],
), ),
Expanded(
child: Obx(
() {
final distinctDates = controller.workDays;
if (distinctDates.isEmpty) {
return const Center(
child: Text(
'Aucune journée de travail trouvée',
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
), ),
if (_dateRange != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('dd/MM/yyyy').format(_dateRange!.start),
style: const TextStyle(fontWeight: FontWeight.bold),
), ),
); const Text(' - '),
} Text(
DateFormat('dd/MM/yyyy').format(_dateRange!.end),
return ListView.builder( style: const TextStyle(fontWeight: FontWeight.bold),
itemCount: distinctDates.length, ),
IconButton(
icon: const Icon(Icons.close, size: 18),
onPressed: () {
setState(() {
_dateRange = null;
});
_filterCommandes();
},
),
],
),
),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _commandes.isEmpty
? const Center(
child: Text('Aucune commande trouvée'),
)
: ListView.builder(
itemCount: _commandes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final date = distinctDates[index]; final commande = _commandes[index];
return Card( return Card(
elevation: 2.0,
margin: const EdgeInsets.symmetric( margin: const EdgeInsets.symmetric(
horizontal: 16.0, horizontal: 8, vertical: 4),
vertical: 8.0,
),
child: ListTile( child: ListTile(
leading: const Icon(Icons.shopping_cart),
title: Text( title: Text(
'Journée du $date', 'Commande #${commande.id} - ${commande.clientNom} ${commande.clientPrenom}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
commande.dateCommande as String
),
Text(
'${commande.montantTotal.toStringAsFixed(2)} DA',
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold),
), ),
],
), ),
leading: const CircleAvatar( trailing: Container(
backgroundColor: Colors.deepOrange, padding: const EdgeInsets.symmetric(
child: Icon( horizontal: 8, vertical: 4),
Icons.calendar_today, decoration: BoxDecoration(
color: Colors.white, color: _getStatutColor(commande.statut)
.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Text(
_getStatutText(commande.statut),
style: TextStyle(
color: _getStatutColor(commande.statut),
fontWeight: FontWeight.bold,
), ),
), ),
trailing: const Icon(
Icons.arrow_forward,
color: Colors.deepOrange,
), ),
onTap: () => navigateToDetailPage(date), onTap: () => _showCommandeDetails(commande),
), ),
); );
}, },
);
},
), ),
), ),
], ],
), ),
); );
} }
String formatDate(String date) {
try {
final parsedDate = DateFormat('dd-MM-yyyy').parse(date);
print('parsedDate1: $parsedDate');
final formattedDate = DateFormat('yyyy-MM-dd').format(parsedDate);
print('formattedDate1: $formattedDate');
return formattedDate;
} catch (e) {
print('Error parsing date: $date');
return '';
}
}
// transformer string en DateTime
void navigateToDetailPage(String selectedDate) {
print('selectedDate: $selectedDate');
DateTime parsedDate = DateFormat('yyyy-MM-dd').parse(selectedDate);
print('parsedDate: $parsedDate');
Get.to(() => HistoryDetailPage(selectedDate: parsedDate));
}
} }

2
lib/Views/listCommandeHistory.dart

@ -32,7 +32,7 @@ class HistoryDetailPage extends StatelessWidget {
init: controller, init: controller,
builder: (controller) { builder: (controller) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Historique de la journée'), appBar: CustomAppBar(title: 'Historique de la journée'),
body: Column( body: Column(
children: [ children: [
Padding( Padding(

5
lib/Views/listUser.dart

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import '../Components/app_bar.dart'; import '../Components/app_bar.dart';
import '../Services/app_database.dart'; //import '../Services/app_database.dart';
import 'editUser.dart'; import 'editUser.dart';
class ListUserPage extends StatefulWidget { class ListUserPage extends StatefulWidget {
@ -35,7 +36,7 @@ class _ListUserPageState extends State<ListUserPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Liste des utilisateurs'), appBar: CustomAppBar(title: 'Liste des utilisateurs'),
body: ListView.builder( body: ListView.builder(
itemCount: userList.length, itemCount: userList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {

66
lib/Views/loginPage.dart

@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/Views/mobilepage.dart';
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground; import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
import 'package:youmazgestion/accueil.dart'; import 'package:youmazgestion/accueil.dart';
import 'package:youmazgestion/Services/app_database.dart';
import '../Models/users.dart'; import '../Models/users.dart';
import '../controller/userController.dart'; import '../controller/userController.dart';
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({super.key}); const LoginPage({super.key});
@ -34,19 +34,7 @@ class _LoginPageState extends State<LoginPage> {
void checkUserCount() async { void checkUserCount() async {
try { try {
final userCount = await AppDatabase.instance.getUserCount(); final userCount = await AppDatabase.instance.getUserCount();
print('Nombre d\'utilisateurs trouvés: $userCount'); // Debug print('Nombre d\'utilisateurs trouvés: $userCount');
// Commentez cette partie pour permettre le login même sans utilisateurs
/*
if (userCount == 0) {
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const AccueilPage()),
);
}
}
*/
} catch (error) { } catch (error) {
print('Erreur lors de la vérification du nombre d\'utilisateurs: $error'); print('Erreur lors de la vérification du nombre d\'utilisateurs: $error');
setState(() { setState(() {
@ -65,11 +53,13 @@ class _LoginPageState extends State<LoginPage> {
Future<void> saveUserData(Users user, String role, int userId) async { Future<void> saveUserData(Users user, String role, int userId) async {
try { try {
// CORRECTION : Utiliser la nouvelle méthode du contrôleur
// Le contrôleur se charge maintenant de tout (observable + SharedPreferences)
userController.setUserWithCredentials(user, role, userId); userController.setUserWithCredentials(user, role, userId);
print('Utilisateur sauvegardé: ${user.username}, rôle: $role, id: $userId'); if (user.pointDeVenteId != null) {
await userController.loadPointDeVenteDesignation();
}
print('Utilisateur sauvegardé avec point de vente: ${userController.pointDeVenteDesignation}');
} catch (error) { } catch (error) {
print('Erreur lors de la sauvegarde: $error'); print('Erreur lors de la sauvegarde: $error');
throw Exception('Erreur lors de la sauvegarde des données utilisateur'); throw Exception('Erreur lors de la sauvegarde des données utilisateur');
@ -82,7 +72,6 @@ class _LoginPageState extends State<LoginPage> {
final String username = _usernameController.text.trim(); final String username = _usernameController.text.trim();
final String password = _passwordController.text.trim(); final String password = _passwordController.text.trim();
// Validation basique
if (username.isEmpty || password.isEmpty) { if (username.isEmpty || password.isEmpty) {
setState(() { setState(() {
_errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe'; _errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe';
@ -98,11 +87,8 @@ class _LoginPageState extends State<LoginPage> {
try { try {
print('Tentative de connexion pour: $username'); print('Tentative de connexion pour: $username');
// Vérification de la connexion à la base de données
final dbInstance = AppDatabase.instance; final dbInstance = AppDatabase.instance;
// Test de connexion à la base
try { try {
final userCount = await dbInstance.getUserCount(); final userCount = await dbInstance.getUserCount();
print('Base de données accessible, $userCount utilisateurs trouvés'); print('Base de données accessible, $userCount utilisateurs trouvés');
@ -110,16 +96,13 @@ class _LoginPageState extends State<LoginPage> {
throw Exception('Impossible d\'accéder à la base de données: $dbError'); throw Exception('Impossible d\'accéder à la base de données: $dbError');
} }
// Vérifier les identifiants
bool isValidUser = await dbInstance.verifyUser(username, password); bool isValidUser = await dbInstance.verifyUser(username, password);
print('Résultat de la vérification: $isValidUser'); print('Résultat de la vérification: $isValidUser');
if (isValidUser) { if (isValidUser) {
// Récupérer les informations complètes de l'utilisateur
Users user = await dbInstance.getUser(username); Users user = await dbInstance.getUser(username);
print('Utilisateur récupéré: ${user.username}'); print('Utilisateur récupéré: ${user.username}');
// Récupérer les credentials
Map<String, dynamic>? userCredentials = Map<String, dynamic>? userCredentials =
await dbInstance.getUserCredentials(username, password); await dbInstance.getUserCredentials(username, password);
@ -128,20 +111,28 @@ class _LoginPageState extends State<LoginPage> {
print('Rôle: ${userCredentials['role']}'); print('Rôle: ${userCredentials['role']}');
print('ID: ${userCredentials['id']}'); print('ID: ${userCredentials['id']}');
// CORRECTION : Sauvegarder ET mettre à jour le contrôleur
await saveUserData( await saveUserData(
user, user,
userCredentials['role'] as String, userCredentials['role'] as String,
userCredentials['id'] as int, userCredentials['id'] as int,
); );
// Navigation // MODIFICATION PRINCIPALE ICI
if (mounted) { if (mounted) {
if (userCredentials['role'] == 'commercial') {
// Redirection vers MainLayout pour les commerciaux
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const MainLayout()),
);
} else {
// Redirection vers AccueilPage pour les autres rôles
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => const AccueilPage()), MaterialPageRoute(builder: (context) => const AccueilPage()),
); );
} }
}
} else { } else {
throw Exception('Erreur lors de la récupération des credentials'); throw Exception('Erreur lors de la récupération des credentials');
} }
@ -265,25 +256,6 @@ class _LoginPageState extends State<LoginPage> {
), ),
), ),
), ),
// Bouton de debug (à supprimer en production)
if (_isErrorVisible)
TextButton(
onPressed: () async {
try {
final count = await AppDatabase.instance.getUserCount();
print('Debug: $count utilisateurs dans la base');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('$count utilisateurs trouvés')),
);
} catch (e) {
print('Debug error: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: $e')),
);
}
},
child: const Text('Debug: Vérifier BDD'),
),
], ],
), ),
), ),

837
lib/Views/mobilepage.dart

@ -0,0 +1,837 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:youmazgestion/Components/QrScan.dart';
import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Components/appDrawer.dart';
import 'package:youmazgestion/Models/client.dart';
import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Models/produit.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/Views/historique.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Youmaz Gestion',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const MainLayout(),
debugShowCheckedModeBanner: false,
);
}
}
class MainLayout extends StatefulWidget {
const MainLayout({super.key});
@override
State<MainLayout> createState() => _MainLayoutState();
}
class _MainLayoutState extends State<MainLayout> {
int _currentIndex = 1; // Index par défaut pour la page de commande
final List<Widget> _pages = [
const HistoriquePage(),
const NouvelleCommandePage(), // Page 1 - Nouvelle commande
const ScanQRPage(), // Page 2 - Scan QR
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _currentIndex == 1 ? CustomAppBar(title: 'Nouvelle Commande') : null,
drawer: CustomDrawer(),
body: _pages[_currentIndex],
bottomNavigationBar: _buildAdaptiveBottomNavBar(),
);
}
Widget _buildAdaptiveBottomNavBar() {
final isDesktop = MediaQuery.of(context).size.width > 600;
return Container(
decoration: BoxDecoration(
border: isDesktop
? const Border(top: BorderSide(color: Colors.grey, width: 0.5))
: null,
),
child: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
// Style adapté pour desktop
type: isDesktop ? BottomNavigationBarType.fixed : BottomNavigationBarType.fixed,
selectedFontSize: isDesktop ? 14 : 12,
unselectedFontSize: isDesktop ? 14 : 12,
iconSize: isDesktop ? 28 : 24,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.history),
label: 'Historique',
),
BottomNavigationBarItem(
icon: Icon(Icons.add_shopping_cart),
label: 'Commande',
),
BottomNavigationBarItem(
icon: Icon(Icons.qr_code_scanner),
label: 'Scan QR',
),
],
),
);
}
}
// Votre code existant pour NouvelleCommandePage reste inchangé
class NouvelleCommandePage extends StatefulWidget {
const NouvelleCommandePage({super.key});
@override
_NouvelleCommandePageState createState() => _NouvelleCommandePageState();
}
class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
final AppDatabase _appDatabase = AppDatabase.instance;
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
// Contrôleurs client
final TextEditingController _nomController = TextEditingController();
final TextEditingController _prenomController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _telephoneController = TextEditingController();
final TextEditingController _adresseController = TextEditingController();
// Panier
final List<Product> _products = [];
final Map<int, int> _quantites = {};
// Utilisateurs commerciaux
List<Users> _commercialUsers = [];
Users? _selectedCommercialUser;
@override
void initState() {
super.initState();
_loadProducts();
_loadCommercialUsers();
}
Future<void> _loadProducts() async {
final products = await _appDatabase.getProducts();
setState(() {
_products.addAll(products);
});
}
Future<void> _loadCommercialUsers() async {
final commercialUsers = await _appDatabase.getCommercialUsers();
setState(() {
_commercialUsers = commercialUsers;
if (_commercialUsers.isNotEmpty) {
_selectedCommercialUser = _commercialUsers.first;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: _buildFloatingCartButton(),
drawer: MediaQuery.of(context).size.width > 600 ? null : CustomDrawer(),
body: Column(
children: [
// Header
Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade600],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.shopping_cart,
color: Colors.blue,
size: 30,
),
),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Nouvelle Commande',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
'Créez une nouvelle commande pour un client',
style: TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
],
),
),
],
),
],
),
),
// Contenu principal
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
),
onPressed: _showClientFormDialog,
child: const Text('Ajouter les informations client'),
),
const SizedBox(height: 20),
_buildProductList(),
],
),
),
),
],
),
);
}
Widget _buildFloatingCartButton() {
return FloatingActionButton.extended(
onPressed: () {
_showCartBottomSheet();
},
icon: const Icon(Icons.shopping_cart),
label: Text('Panier (${_quantites.values.where((q) => q > 0).length})'),
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
);
}
void _showClientFormDialog() {
Get.dialog(
AlertDialog(
title: const Text('Informations Client'),
content: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildTextFormField(
controller: _nomController,
label: 'Nom',
validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un nom' : null,
),
const SizedBox(height: 12),
_buildTextFormField(
controller: _prenomController,
label: 'Prénom',
validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un prénom' : null,
),
const SizedBox(height: 12),
_buildTextFormField(
controller: _emailController,
label: 'Email',
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value?.isEmpty ?? true) return 'Veuillez entrer un email';
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value!)) {
return 'Email invalide';
}
return null;
},
),
const SizedBox(height: 12),
_buildTextFormField(
controller: _telephoneController,
label: 'Téléphone',
keyboardType: TextInputType.phone,
validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un téléphone' : null,
),
const SizedBox(height: 12),
_buildTextFormField(
controller: _adresseController,
label: 'Adresse',
maxLines: 2,
validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer une adresse' : null,
),
const SizedBox(height: 12),
_buildCommercialDropdown(),
],
),
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('Annuler'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
),
onPressed: () {
if (_formKey.currentState!.validate()) {
Get.back();
Get.snackbar(
'Succès',
'Informations client enregistrées',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
);
}
},
child: const Text('Enregistrer'),
),
],
),
);
}
Widget _buildTextFormField({
required TextEditingController controller,
required String label,
TextInputType? keyboardType,
String? Function(String?)? validator,
int? maxLines,
}) {
return TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: label,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade400),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade400),
),
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
keyboardType: keyboardType,
validator: validator,
maxLines: maxLines,
);
}
Widget _buildCommercialDropdown() {
return DropdownButtonFormField<Users>(
value: _selectedCommercialUser,
decoration: InputDecoration(
labelText: 'Commercial',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.white,
),
items: _commercialUsers.map((Users user) {
return DropdownMenuItem<Users>(
value: user,
child: Text('${user.name} ${user.lastName}'),
);
}).toList(),
onChanged: (Users? newValue) {
setState(() {
_selectedCommercialUser = newValue;
});
},
validator: (value) => value == null ? 'Veuillez sélectionner un commercial' : null,
);
}
Widget _buildProductList() {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Produits Disponibles',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 9, 56, 95),
),
),
const SizedBox(height: 16),
_products.isEmpty
? const Center(child: CircularProgressIndicator())
: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
final quantity = _quantites[product.id] ?? 0;
return _buildProductListItem(product, quantity);
},
),
],
),
),
);
}
Widget _buildProductListItem(Product product, int quantity) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.shopping_bag, color: Colors.blue),
),
title: Text(
product.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
'${product.price.toStringAsFixed(2)} DA',
style: TextStyle(
color: Colors.green.shade700,
fontWeight: FontWeight.w600,
),
),
if (product.stock != null)
Text(
'Stock: ${product.stock}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
trailing: Container(
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove, size: 18),
onPressed: () {
if (quantity > 0) {
setState(() {
_quantites[product.id!] = quantity - 1;
});
}
},
),
Text(
quantity.toString(),
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.add, size: 18),
onPressed: () {
if (product.stock == null || quantity < product.stock!) {
setState(() {
_quantites[product.id!] = quantity + 1;
});
} else {
Get.snackbar(
'Stock insuffisant',
'Quantité demandée non disponible',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
},
),
],
),
),
),
);
}
void _showCartBottomSheet() {
Get.bottomSheet(
Container(
height: MediaQuery.of(context).size.height * 0.7,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Votre Panier',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Get.back(),
),
],
),
const Divider(),
Expanded(child: _buildCartItemsList()),
const Divider(),
_buildCartTotalSection(),
const SizedBox(height: 16),
_buildSubmitButton(),
],
),
),
isScrollControlled: true,
);
}
Widget _buildCartItemsList() {
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
if (itemsInCart.isEmpty) {
return const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.shopping_cart_outlined, size: 60, color: Colors.grey),
SizedBox(height: 16),
Text(
'Votre panier est vide',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
);
}
return ListView.builder(
itemCount: itemsInCart.length,
itemBuilder: (context, index) {
final entry = itemsInCart[index];
final product = _products.firstWhere((p) => p.id == entry.key);
return Dismissible(
key: Key(entry.key.toString()),
background: Container(
color: Colors.red.shade100,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
child: const Icon(Icons.delete, color: Colors.red),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
_quantites.remove(entry.key);
});
Get.snackbar(
'Produit retiré',
'${product.name} a été retiré du panier',
snackPosition: SnackPosition.BOTTOM,
);
},
child: Card(
margin: const EdgeInsets.only(bottom: 8),
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.shopping_bag, size: 20),
),
title: Text(product.name),
subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} DA'),
trailing: Text(
'${(entry.value * product.price).toStringAsFixed(2)} DA',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue.shade800,
),
),
),
),
);
},
);
}
Widget _buildCartTotalSection() {
double total = 0;
_quantites.forEach((productId, quantity) {
final product = _products.firstWhere((p) => p.id == productId);
total += quantity * product.price;
});
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Total:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
'${total.toStringAsFixed(2)} DA',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
],
),
const SizedBox(height: 8),
Text(
'${_quantites.values.where((q) => q > 0).length} article(s)',
style: TextStyle(color: Colors.grey.shade600),
),
],
);
}
Widget _buildSubmitButton() {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
),
onPressed: _submitOrder,
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
'Valider la Commande',
style: TextStyle(fontSize: 16),
),
),
);
}
Future<void> _submitOrder() async {
if (_nomController.text.isEmpty ||
_prenomController.text.isEmpty ||
_emailController.text.isEmpty ||
_telephoneController.text.isEmpty ||
_adresseController.text.isEmpty) {
Get.back(); // Ferme le bottom sheet
Get.snackbar(
'Informations manquantes',
'Veuillez remplir les informations client',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
_showClientFormDialog();
return;
}
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
if (itemsInCart.isEmpty) {
Get.snackbar(
'Panier vide',
'Veuillez ajouter des produits à votre commande',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
setState(() {
_isLoading = true;
});
// Créer le client
final client = Client(
nom: _nomController.text,
prenom: _prenomController.text,
email: _emailController.text,
telephone: _telephoneController.text,
adresse: _adresseController.text,
dateCreation: DateTime.now(),
);
// Calculer le total et préparer les détails
double total = 0;
final details = <DetailCommande>[];
for (final entry in itemsInCart) {
final product = _products.firstWhere((p) => p.id == entry.key);
total += entry.value * product.price;
details.add(DetailCommande(
commandeId: 0,
produitId: product.id!,
quantite: entry.value,
prixUnitaire: product.price,
sousTotal: entry.value * product.price,
));
}
// Créer la commande
final commande = Commande(
clientId: 0,
dateCommande: DateTime.now(),
statut: StatutCommande.enAttente,
montantTotal: total,
notes: 'Commande passée via l\'application',
commandeurId: _selectedCommercialUser?.id,
);
try {
await _appDatabase.createCommandeComplete(client, commande, details);
Get.back(); // Ferme le bottom sheet
// Afficher le dialogue de confirmation
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Commande Validée'),
content: const Text('Votre commande a été enregistrée avec succès.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
// Réinitialiser le formulaire
_nomController.clear();
_prenomController.clear();
_emailController.clear();
_telephoneController.clear();
_adresseController.clear();
setState(() {
_quantites.clear();
_isLoading = false;
});
},
child: const Text('OK'),
),
],
),
);
} catch (e) {
setState(() {
_isLoading = false;
});
Get.snackbar(
'Erreur',
'Une erreur est survenue: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
@override
void dispose() {
_nomController.dispose();
_prenomController.dispose();
_emailController.dispose();
_telephoneController.dispose();
_adresseController.dispose();
super.dispose();
}
}

519
lib/Views/newCommand.dart

@ -3,8 +3,9 @@ import 'package:get/get.dart';
import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/app_bar.dart';
import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Components/appDrawer.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Models/produit.dart'; import 'package:youmazgestion/Models/produit.dart';
import 'package:youmazgestion/Services/productDatabase.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
class NouvelleCommandePage extends StatefulWidget { class NouvelleCommandePage extends StatefulWidget {
const NouvelleCommandePage({super.key}); const NouvelleCommandePage({super.key});
@ -14,10 +15,11 @@ class NouvelleCommandePage extends StatefulWidget {
} }
class _NouvelleCommandePageState extends State<NouvelleCommandePage> { class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
final ProductDatabase _database = ProductDatabase.instance; final AppDatabase _appDatabase = AppDatabase.instance;
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
// Informations client // Contrôleurs client
final TextEditingController _nomController = TextEditingController(); final TextEditingController _nomController = TextEditingController();
final TextEditingController _prenomController = TextEditingController(); final TextEditingController _prenomController = TextEditingController();
final TextEditingController _emailController = TextEditingController(); final TextEditingController _emailController = TextEditingController();
@ -26,77 +28,77 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
// Panier // Panier
final List<Product> _products = []; final List<Product> _products = [];
final Map<int, int> _quantites = {}; // productId -> quantity final Map<int, int> _quantites = {};
// Utilisateurs commerciaux
List<Users> _commercialUsers = [];
Users? _selectedCommercialUser;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadProducts(); _loadProducts();
_loadCommercialUsers();
} }
Future<void> _loadProducts() async { Future<void> _loadProducts() async {
final products = await _database.getProducts(); final products = await _appDatabase.getProducts();
setState(() { setState(() {
_products.addAll(products); _products.addAll(products);
}); });
} }
Future<void> _loadCommercialUsers() async {
final commercialUsers = await _appDatabase.getCommercialUsers();
setState(() {
_commercialUsers = commercialUsers;
if (_commercialUsers.isNotEmpty) {
_selectedCommercialUser = _commercialUsers.first;
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: const CustomAppBar(title: 'Nouvelle Commande'), floatingActionButton: _buildFloatingCartButton(),
appBar: CustomAppBar(title: 'Nouvelle Commande'),
drawer: CustomDrawer(), drawer: CustomDrawer(),
body: Column( body: Column(
children: [ children: [
// Header avec logo et titre // Header
Container( Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [Colors.blue.shade50, Colors.white], colors: [Colors.blue.shade800, Colors.blue.shade600],
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
), ),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
), ),
child: Column( child: Column(
children: [ children: [
// Logo et titre
Row( Row(
children: [ children: [
// Logo de l'entreprise
Container( Container(
width: 50, width: 50,
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.asset(
'assets/logo.png',
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
decoration: BoxDecoration(
color: Colors.blue.shade800,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Icon( child: const Icon(
Icons.shopping_cart, Icons.shopping_cart,
color: Colors.white, color: Colors.blue,
size: 30, size: 30,
), ),
);
},
),
),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
const Expanded( const Expanded(
@ -108,14 +110,14 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: Colors.white,
), ),
), ),
Text( Text(
'Créez une nouvelle commande pour un client', 'Créez une nouvelle commande pour un client',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.grey, color: Colors.white70,
), ),
), ),
], ],
@ -134,15 +136,17 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildClientForm(), ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
),
onPressed: _showClientFormDialog,
child: const Text('Ajouter les informations client'),
),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildProductList(), _buildProductList(),
const SizedBox(height: 20),
_buildCartSection(),
const SizedBox(height: 20),
_buildTotalSection(),
const SizedBox(height: 20),
_buildSubmitButton(),
], ],
), ),
), ),
@ -152,123 +156,153 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
); );
} }
Widget _buildClientForm() { Widget _buildFloatingCartButton() {
return Card( return FloatingActionButton.extended(
elevation: 4, onPressed: () {
shape: RoundedRectangleBorder( _showCartBottomSheet();
borderRadius: BorderRadius.circular(12), },
), icon: const Icon(Icons.shopping_cart),
child: Padding( label: Text('Panier (${_quantites.values.where((q) => q > 0).length})'),
padding: const EdgeInsets.all(16.0), backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
);
}
void _showClientFormDialog() {
Get.dialog(
AlertDialog(
title: const Text('Informations Client'),
content: SingleChildScrollView(
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( _buildTextFormField(
'Informations Client',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
TextFormField(
controller: _nomController, controller: _nomController,
decoration: InputDecoration( label: 'Nom',
labelText: 'Nom', validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un nom' : null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
), ),
filled: true, const SizedBox(height: 12),
fillColor: Colors.white, _buildTextFormField(
controller: _prenomController,
label: 'Prénom',
validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un prénom' : null,
), ),
const SizedBox(height: 12),
_buildTextFormField(
controller: _emailController,
label: 'Email',
keyboardType: TextInputType.emailAddress,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value?.isEmpty ?? true) return 'Veuillez entrer un email';
return 'Veuillez entrer un nom'; if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value!)) {
return 'Email invalide';
} }
return null; return null;
}, },
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
TextFormField( _buildTextFormField(
controller: _prenomController, controller: _telephoneController,
decoration: InputDecoration( label: 'Téléphone',
labelText: 'Prénom', keyboardType: TextInputType.phone,
border: OutlineInputBorder( validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un téléphone' : null,
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.white,
), ),
validator: (value) { const SizedBox(height: 12),
if (value == null || value.isEmpty) { _buildTextFormField(
return 'Veuillez entrer un prénom'; controller: _adresseController,
} label: 'Adresse',
return null; maxLines: 2,
}, validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer une adresse' : null,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
TextFormField( _buildCommercialDropdown(),
controller: _emailController, ],
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
), ),
filled: true,
fillColor: Colors.white,
), ),
keyboardType: TextInputType.emailAddress, ),
validator: (value) { actions: [
if (value == null || value.isEmpty) { TextButton(
return 'Veuillez entrer un email'; onPressed: () => Get.back(),
} child: const Text('Annuler'),
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) { ),
return 'Veuillez entrer un email valide'; ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
),
onPressed: () {
if (_formKey.currentState!.validate()) {
Get.back();
Get.snackbar(
'Succès',
'Informations client enregistrées',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
);
} }
return null;
}, },
child: const Text('Enregistrer'),
), ),
const SizedBox(height: 12), ],
TextFormField( ),
controller: _telephoneController, );
}
Widget _buildTextFormField({
required TextEditingController controller,
required String label,
TextInputType? keyboardType,
String? Function(String?)? validator,
int? maxLines,
}) {
return TextFormField(
controller: controller,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Téléphone', labelText: label,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade400),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade400),
), ),
filled: true, filled: true,
fillColor: Colors.white, fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
), ),
keyboardType: TextInputType.phone, keyboardType: keyboardType,
validator: (value) { validator: validator,
if (value == null || value.isEmpty) { maxLines: maxLines,
return 'Veuillez entrer un numéro de téléphone'; );
} }
return null;
}, Widget _buildCommercialDropdown() {
), return DropdownButtonFormField<Users>(
const SizedBox(height: 12), value: _selectedCommercialUser,
TextFormField(
controller: _adresseController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Adresse de livraison', labelText: 'Commercial',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
filled: true, filled: true,
fillColor: Colors.white, fillColor: Colors.white,
), ),
maxLines: 2, items: _commercialUsers.map((Users user) {
validator: (value) { return DropdownMenuItem<Users>(
if (value == null || value.isEmpty) { value: user,
return 'Veuillez entrer une adresse'; child: Text('${user.name} ${user.lastName}'),
} );
return null; }).toList(),
onChanged: (Users? newValue) {
setState(() {
_selectedCommercialUser = newValue;
});
}, },
), validator: (value) => value == null ? 'Veuillez sélectionner un commercial' : null,
],
),
),
),
); );
} }
@ -285,7 +319,11 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
children: [ children: [
const Text( const Text(
'Produits Disponibles', 'Produits Disponibles',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 9, 56, 95),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_products.isEmpty _products.isEmpty
@ -298,6 +336,16 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
final product = _products[index]; final product = _products[index];
final quantity = _quantites[product.id] ?? 0; final quantity = _quantites[product.id] ?? 0;
return _buildProductListItem(product, quantity);
},
),
],
),
),
);
}
Widget _buildProductListItem(Product product, int quantity) {
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 8), margin: const EdgeInsets.symmetric(vertical: 8),
elevation: 2, elevation: 2,
@ -316,8 +364,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
color: Colors.blue.shade50, color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Icon(Icons.shopping_bag, child: const Icon(Icons.shopping_bag, color: Colors.blue),
color: Colors.blue),
), ),
title: Text( title: Text(
product.name, product.name,
@ -378,6 +425,8 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
'Stock insuffisant', 'Stock insuffisant',
'Quantité demandée non disponible', 'Quantité demandée non disponible',
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
); );
} }
}, },
@ -387,63 +436,97 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
), ),
), ),
); );
}, }
void _showCartBottomSheet() {
Get.bottomSheet(
Container(
height: MediaQuery.of(context).size.height * 0.7,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Votre Panier',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Get.back(),
),
],
), ),
const Divider(),
Expanded(child: _buildCartItemsList()),
const Divider(),
_buildCartTotalSection(),
const SizedBox(height: 16),
_buildSubmitButton(),
], ],
), ),
), ),
isScrollControlled: true,
); );
} }
Widget _buildCartSection() { Widget _buildCartItemsList() {
final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList(); final itemsInCart = _quantites.entries.where((e) => e.value > 0).toList();
if (itemsInCart.isEmpty) { if (itemsInCart.isEmpty) {
return Card( return const Center(
elevation: 4, child: Column(
shape: RoundedRectangleBorder( mainAxisSize: MainAxisSize.min,
borderRadius: BorderRadius.circular(12), children: [
), Icon(Icons.shopping_cart_outlined, size: 60, color: Colors.grey),
child: const Padding( SizedBox(height: 16),
padding: EdgeInsets.all(16.0), Text(
child: Center(
child: Text(
'Votre panier est vide', 'Votre panier est vide',
style: TextStyle(color: Colors.grey), style: TextStyle(fontSize: 16, color: Colors.grey),
),
), ),
],
), ),
); );
} }
return Card( return ListView.builder(
elevation: 4, itemCount: itemsInCart.length,
shape: RoundedRectangleBorder( itemBuilder: (context, index) {
borderRadius: BorderRadius.circular(12), final entry = itemsInCart[index];
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Votre Panier',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
...itemsInCart.map((entry) {
final product = _products.firstWhere((p) => p.id == entry.key); final product = _products.firstWhere((p) => p.id == entry.key);
return Card(
return Dismissible(
key: Key(entry.key.toString()),
background: Container(
color: Colors.red.shade100,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
child: const Icon(Icons.delete, color: Colors.red),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
_quantites.remove(entry.key);
});
Get.snackbar(
'Produit retiré',
'${product.name} a été retiré du panier',
snackPosition: SnackPosition.BOTTOM,
);
},
child: Card(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: ListTile( child: ListTile(
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
horizontal: 16,
vertical: 8,
),
leading: Container( leading: Container(
width: 40, width: 40,
height: 40, height: 40,
@ -463,35 +546,22 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
), ),
), ),
), ),
);
}),
],
),
), ),
); );
},
);
} }
Widget _buildTotalSection() { Widget _buildCartTotalSection() {
double total = 0; double total = 0;
_quantites.forEach((productId, quantity) { _quantites.forEach((productId, quantity) {
final product = _products.firstWhere((p) => p.id == productId); final product = _products.firstWhere((p) => p.id == productId);
total += quantity * product.price; total += quantity * product.price;
}); });
return Card( return Column(
elevation: 4, children: [
shape: RoundedRectangleBorder( Row(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( const Text(
@ -508,31 +578,61 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
), ),
], ],
), ),
const SizedBox(height: 8),
Text(
'${_quantites.values.where((q) => q > 0).length} article(s)',
style: TextStyle(color: Colors.grey.shade600),
), ),
), ],
); );
} }
Widget _buildSubmitButton() { Widget _buildSubmitButton() {
return ElevatedButton( return SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blue.shade600, backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
elevation: 4, elevation: 4,
), ),
onPressed: _submitOrder, onPressed: _submitOrder,
child: const Text( child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text(
'Valider la Commande', 'Valider la Commande',
style: TextStyle(fontSize: 16, color: Colors.white), style: TextStyle(fontSize: 16),
),
), ),
); );
} }
Future<void> _submitOrder() async { Future<void> _submitOrder() async {
if (!_formKey.currentState!.validate()) { if (_nomController.text.isEmpty ||
_prenomController.text.isEmpty ||
_emailController.text.isEmpty ||
_telephoneController.text.isEmpty ||
_adresseController.text.isEmpty) {
Get.back(); // Ferme le bottom sheet
Get.snackbar(
'Informations manquantes',
'Veuillez remplir les informations client',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
);
_showClientFormDialog();
return; return;
} }
@ -542,10 +642,16 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
'Panier vide', 'Panier vide',
'Veuillez ajouter des produits à votre commande', 'Veuillez ajouter des produits à votre commande',
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white,
); );
return; return;
} }
setState(() {
_isLoading = true;
});
// Créer le client // Créer le client
final client = Client( final client = Client(
nom: _nomController.text, nom: _nomController.text,
@ -565,7 +671,7 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
total += entry.value * product.price; total += entry.value * product.price;
details.add(DetailCommande( details.add(DetailCommande(
commandeId: 0, // Valeur temporaire, sera remplacée dans la transaction commandeId: 0,
produitId: product.id!, produitId: product.id!,
quantite: entry.value, quantite: entry.value,
prixUnitaire: product.price, prixUnitaire: product.price,
@ -575,35 +681,54 @@ class _NouvelleCommandePageState extends State<NouvelleCommandePage> {
// Créer la commande // Créer la commande
final commande = Commande( final commande = Commande(
clientId: 0, // sera mis à jour après création du client clientId: 0,
dateCommande: DateTime.now(), dateCommande: DateTime.now(),
statut: StatutCommande.enAttente, statut: StatutCommande.enAttente,
montantTotal: total, montantTotal: total,
notes: 'Commande passée via l\'application', notes: 'Commande passée via l\'application',
commandeurId: _selectedCommercialUser?.id,
); );
try { try {
// Enregistrer la commande dans la base de données await _appDatabase.createCommandeComplete(client, commande, details);
await _database.createCommandeComplete(client, commande, details);
Get.snackbar( Get.back(); // Ferme le bottom sheet
'Succès',
'Votre commande a été enregistrée',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white,
);
// Afficher le dialogue de confirmation
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Commande Validée'),
content: const Text('Votre commande a été enregistrée avec succès.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
// Réinitialiser le formulaire // Réinitialiser le formulaire
_formKey.currentState!.reset(); _nomController.clear();
_prenomController.clear();
_emailController.clear();
_telephoneController.clear();
_adresseController.clear();
setState(() { setState(() {
_quantites.clear(); _quantites.clear();
_isLoading = false;
}); });
},
child: const Text('OK'),
),
],
),
);
} catch (e) { } catch (e) {
setState(() {
_isLoading = false;
});
Get.snackbar( Get.snackbar(
'Erreur', 'Erreur',
'Une erreur est survenue lors de l\'enregistrement de la commande: ${e.toString()}', 'Une erreur est survenue: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red, backgroundColor: Colors.red,
colorText: Colors.white, colorText: Colors.white,

2
lib/Views/produitsCard.dart

@ -222,7 +222,7 @@ class _ProductCardState extends State<ProductCard> with TickerProviderStateMixin
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'${widget.product.price.toStringAsFixed(2)} FCFA', '${widget.product.price.toStringAsFixed(2)} MGA',
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 13, fontSize: 13,

70
lib/Views/registrationPage.dart

@ -1,9 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Models/role.dart'; import 'package:youmazgestion/Models/role.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/accueil.dart'; import 'package:youmazgestion/accueil.dart';
import '../Services/app_database.dart'; // Changé de authDatabase.dart //import '../Services/app_database.dart'; // Changé de authDatabase.dart
class RegistrationPage extends StatefulWidget { class RegistrationPage extends StatefulWidget {
const RegistrationPage({super.key}); const RegistrationPage({super.key});
@ -23,7 +24,9 @@ class _RegistrationPageState extends State<RegistrationPage> {
Role? _selectedRole; Role? _selectedRole;
bool _isLoading = false; bool _isLoading = false;
bool _isLoadingRoles = true; bool _isLoadingRoles = true;
List<Map<String, dynamic>> _availablePointsDeVente = [];
int? _selectedPointDeVenteId;
bool _isLoadingPointsDeVente = true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -40,6 +43,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
try { try {
await AppDatabase.instance.initDatabase(); await AppDatabase.instance.initDatabase();
await _loadRoles(); await _loadRoles();
await _loadPointsDeVente(); // Ajouté ici
} catch (error) { } catch (error) {
print('Erreur lors de l\'initialisation: $error'); print('Erreur lors de l\'initialisation: $error');
if (mounted) { if (mounted) {
@ -48,7 +52,25 @@ class _RegistrationPageState extends State<RegistrationPage> {
} }
} }
} }
Future<void> _loadPointsDeVente() async {
try {
final points = await AppDatabase.instance.getPointsDeVente();
if (mounted) {
setState(() {
_availablePointsDeVente = points;
_isLoadingPointsDeVente = false;
});
}
} catch (error) {
print('Erreur lors du chargement des points de vente: $error');
if (mounted) {
setState(() {
_isLoadingPointsDeVente = false;
});
}
}
}
Future<void> _loadRoles() async { Future<void> _loadRoles() async {
try { try {
final roles = await AppDatabase.instance.getRoles(); final roles = await AppDatabase.instance.getRoles();
@ -103,7 +125,8 @@ class _RegistrationPageState extends State<RegistrationPage> {
_emailController.text.trim().isEmpty || _emailController.text.trim().isEmpty ||
_usernameController.text.trim().isEmpty || _usernameController.text.trim().isEmpty ||
_passwordController.text.trim().isEmpty || _passwordController.text.trim().isEmpty ||
_selectedRole == null) { _selectedRole == null ||
_selectedPointDeVenteId == null) { // Ajouté ici
_showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs.'); _showErrorDialog('Champs manquants', 'Veuillez remplir tous les champs.');
return false; return false;
} }
@ -142,6 +165,7 @@ class _RegistrationPageState extends State<RegistrationPage> {
username: _usernameController.text.trim(), username: _usernameController.text.trim(),
roleId: _selectedRole!.id!, // Utiliser l'ID du rôle roleId: _selectedRole!.id!, // Utiliser l'ID du rôle
roleName: _selectedRole!.designation, // Pour l'affichage roleName: _selectedRole!.designation, // Pour l'affichage
pointDeVenteId: _selectedPointDeVenteId, // Ajouté ici
); );
// Sauvegarder l'utilisateur dans la base de données // Sauvegarder l'utilisateur dans la base de données
@ -361,6 +385,46 @@ class _RegistrationPageState extends State<RegistrationPage> {
), ),
), ),
), ),
// Dans la méthode build, après le DropdownButton des rôles
const SizedBox(height: 16.0),
_isLoadingPointsDeVente
? const CircularProgressIndicator()
: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _selectedPointDeVenteId,
hint: const Text('Sélectionner un point de vente'),
isExpanded: true,
onChanged: _isLoading
? null
: (int? newValue) {
setState(() {
_selectedPointDeVenteId = newValue;
});
},
items: _availablePointsDeVente
.map<DropdownMenuItem<int>>((Map<String, dynamic> point) {
return DropdownMenuItem<int>(
value: point['id'] as int,
child: Row(
children: [
const Icon(Icons.store, size: 20),
const SizedBox(width: 8),
Text(point['designation'] as String),
],
),
);
}).toList(),
),
),
),
const SizedBox(height: 16.0),
const SizedBox(height: 24.0), const SizedBox(height: 24.0),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,

5
lib/accueil.dart

@ -3,6 +3,7 @@ import 'package:intl/intl.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground; import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
import 'package:youmazgestion/Views/produitsCard.dart'; import 'package:youmazgestion/Views/produitsCard.dart';
import 'Components/appDrawer.dart'; import 'Components/appDrawer.dart';
@ -10,7 +11,7 @@ import 'Components/app_bar.dart';
import 'Components/cartItem.dart'; import 'Components/cartItem.dart';
import 'Models/produit.dart'; import 'Models/produit.dart';
import 'Services/OrderDatabase.dart'; import 'Services/OrderDatabase.dart';
import 'Services/productDatabase.dart'; //import 'Services/productDatabase.dart';
import 'Views/ticketPage.dart'; import 'Views/ticketPage.dart';
import 'controller/userController.dart'; import 'controller/userController.dart';
import 'my_app.dart'; import 'my_app.dart';
@ -25,7 +26,7 @@ class AccueilPage extends StatefulWidget {
class _AccueilPageState extends State<AccueilPage> { class _AccueilPageState extends State<AccueilPage> {
final UserController userController = Get.put(UserController()); final UserController userController = Get.put(UserController());
final ProductDatabase productDatabase = ProductDatabase(); final AppDatabase productDatabase = AppDatabase.instance;
late Future<Map<String, List<Product>>> productsFuture; late Future<Map<String, List<Product>>> productsFuture;
final OrderDatabase orderDatabase = OrderDatabase.instance; final OrderDatabase orderDatabase = OrderDatabase.instance;
final WorkDatabase workDatabase = WorkDatabase.instance; final WorkDatabase workDatabase = WorkDatabase.instance;

5
lib/controller/AccueilController.dart

@ -1,17 +1,18 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
import '../Components/cartItem.dart'; import '../Components/cartItem.dart';
import '../Models/produit.dart'; import '../Models/produit.dart';
import '../Services/OrderDatabase.dart'; import '../Services/OrderDatabase.dart';
import '../Services/WorkDatabase.dart'; import '../Services/WorkDatabase.dart';
import '../Services/productDatabase.dart'; //import '../Services/productDatabase.dart';
import '../Views/ticketPage.dart'; import '../Views/ticketPage.dart';
import '../my_app.dart'; import '../my_app.dart';
class AccueilController extends GetxController { class AccueilController extends GetxController {
final UserController userController = Get.find(); final UserController userController = Get.find();
final ProductDatabase productDatabase = ProductDatabase(); final AppDatabase productDatabase = AppDatabase.instance;
final Rx<Map<String, List<Product>>> productsFuture = Rx({}); // Observable final Rx<Map<String, List<Product>>> productsFuture = Rx({}); // Observable
final OrderDatabase orderDatabase = OrderDatabase.instance; final OrderDatabase orderDatabase = OrderDatabase.instance;
final WorkDatabase workDatabase = WorkDatabase.instance; final WorkDatabase workDatabase = WorkDatabase.instance;

63
lib/controller/userController.dart

@ -1,7 +1,8 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/users.dart';
import 'package:youmazgestion/Services/app_database.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
//import 'package:youmazgestion/Services/app_database.dart';
class UserController extends GetxController { class UserController extends GetxController {
final _username = ''.obs; final _username = ''.obs;
@ -11,6 +12,8 @@ class UserController extends GetxController {
final _lastname = ''.obs; final _lastname = ''.obs;
final _password = ''.obs; final _password = ''.obs;
final _userId = 0.obs; // Ajout de l'ID utilisateur final _userId = 0.obs; // Ajout de l'ID utilisateur
final _pointDeVenteId = 0.obs;
final _pointDeVenteDesignation = ''.obs;
String get username => _username.value; String get username => _username.value;
String get email => _email.value; String get email => _email.value;
@ -19,6 +22,8 @@ class UserController extends GetxController {
String get lastname => _lastname.value; String get lastname => _lastname.value;
String get password => _password.value; String get password => _password.value;
int get userId => _userId.value; int get userId => _userId.value;
int get pointDeVenteId => _pointDeVenteId.value;
String get pointDeVenteDesignation => _pointDeVenteDesignation.value;
@override @override
void onInit() { void onInit() {
@ -34,41 +39,57 @@ class UserController extends GetxController {
final storedUsername = prefs.getString('username') ?? ''; final storedUsername = prefs.getString('username') ?? '';
final storedRole = prefs.getString('role') ?? ''; final storedRole = prefs.getString('role') ?? '';
final storedUserId = prefs.getInt('user_id') ?? 0; final storedUserId = prefs.getInt('user_id') ?? 0;
final storedPointDeVenteId = prefs.getInt('point_de_vente_id') ?? 0;
final storedPointDeVenteDesignation = prefs.getString('point_de_vente_designation') ?? '';
if (storedUsername.isNotEmpty) { if (storedUsername.isNotEmpty) {
try { try {
// Récupérer les données complètes depuis la base de données
Users user = await AppDatabase.instance.getUser(storedUsername); Users user = await AppDatabase.instance.getUser(storedUsername);
// Mettre à jour TOUTES les données
_username.value = user.username; _username.value = user.username;
_email.value = user.email; _email.value = user.email;
_name.value = user.name; _name.value = user.name;
_lastname.value = user.lastName; _lastname.value = user.lastName;
_password.value = user.password; _password.value = user.password;
_role.value = storedRole; // Récupéré depuis SharedPreferences _role.value = storedRole;
_userId.value = storedUserId; // Récupéré depuis SharedPreferences _userId.value = storedUserId;
_pointDeVenteId.value = storedPointDeVenteId;
_pointDeVenteDesignation.value = storedPointDeVenteDesignation;
// Si la désignation n'est pas sauvegardée, on peut la récupérer
if (_pointDeVenteDesignation.value.isEmpty && _pointDeVenteId.value > 0) {
await loadPointDeVenteDesignation();
}
print("✅ Données chargées depuis la DB - Username: ${_username.value}");
print("✅ Name: ${_name.value}, Email: ${_email.value}");
print("✅ Role: ${_role.value}, UserID: ${_userId.value}");
} catch (dbError) { } catch (dbError) {
print('❌ Erreur DB, chargement depuis SharedPreferences uniquement: $dbError'); // Fallback
// Fallback : charger depuis SharedPreferences uniquement
_username.value = storedUsername; _username.value = storedUsername;
_email.value = prefs.getString('email') ?? ''; _email.value = prefs.getString('email') ?? '';
_role.value = storedRole; _role.value = storedRole;
_name.value = prefs.getString('name') ?? ''; _name.value = prefs.getString('name') ?? '';
_lastname.value = prefs.getString('lastname') ?? ''; _lastname.value = prefs.getString('lastname') ?? '';
_userId.value = storedUserId; _userId.value = storedUserId;
_pointDeVenteId.value = storedPointDeVenteId;
_pointDeVenteDesignation.value = storedPointDeVenteDesignation;
} }
} else {
print("❌ Aucun utilisateur stocké trouvé");
} }
} catch (e) { } catch (e) {
print('❌ Erreur lors du chargement des données utilisateur: $e'); print('❌ Erreur lors du chargement des données utilisateur: $e');
} }
} }
Future<void> loadPointDeVenteDesignation() async {
if (_pointDeVenteId.value <= 0) return;
try {
final pointDeVente = await AppDatabase.instance.getPointDeVenteById(_pointDeVenteId.value);
if (pointDeVente != null) {
_pointDeVenteDesignation.value = pointDeVente['designation'] as String;
await saveUserData(); // Sauvegarder la désignation
}
} catch (e) {
print('❌ Erreur lors du chargement de la désignation du point de vente: $e');
}
}
// NOUVELLE MÉTHODE : Mise à jour complète avec Users + credentials // NOUVELLE MÉTHODE : Mise à jour complète avec Users + credentials
void setUserWithCredentials(Users user, String role, int userId) { void setUserWithCredentials(Users user, String role, int userId) {
@ -79,6 +100,7 @@ class UserController extends GetxController {
_lastname.value = user.lastName; _lastname.value = user.lastName;
_password.value = user.password; _password.value = user.password;
_userId.value = userId; // ID depuis les credentials _userId.value = userId; // ID depuis les credentials
_pointDeVenteId.value = user.pointDeVenteId ?? 0;
print("✅ Utilisateur mis à jour avec credentials:"); print("✅ Utilisateur mis à jour avec credentials:");
print(" Username: ${_username.value}"); print(" Username: ${_username.value}");
@ -119,7 +141,9 @@ class UserController extends GetxController {
await prefs.setString('role', _role.value); await prefs.setString('role', _role.value);
await prefs.setString('name', _name.value); await prefs.setString('name', _name.value);
await prefs.setString('lastname', _lastname.value); await prefs.setString('lastname', _lastname.value);
await prefs.setInt('user_id', _userId.value); // Sauvegarder l'ID await prefs.setInt('user_id', _userId.value);
await prefs.setInt('point_de_vente_id', _pointDeVenteId.value);
await prefs.setString('point_de_vente_designation', _pointDeVenteDesignation.value);
print("✅ Données sauvegardées avec succès dans SharedPreferences"); print("✅ Données sauvegardées avec succès dans SharedPreferences");
} catch (e) { } catch (e) {
@ -132,24 +156,25 @@ class UserController extends GetxController {
try { try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
// Vider SharedPreferences
await prefs.remove('username'); await prefs.remove('username');
await prefs.remove('email'); await prefs.remove('email');
await prefs.remove('role'); await prefs.remove('role');
await prefs.remove('name'); await prefs.remove('name');
await prefs.remove('lastname'); await prefs.remove('lastname');
await prefs.remove('user_id'); // Supprimer l'ID aussi await prefs.remove('user_id');
await prefs.remove('point_de_vente_id');
await prefs.remove('point_de_vente_designation');
// Vider les variables observables
_username.value = ''; _username.value = '';
_email.value = ''; _email.value = '';
_role.value = ''; _role.value = '';
_name.value = ''; _name.value = '';
_lastname.value = ''; _lastname.value = '';
_password.value = ''; _password.value = '';
_userId.value = 0; // Réinitialiser l'ID _userId.value = 0;
_pointDeVenteId.value = 0;
_pointDeVenteDesignation.value = '';
print("✅ Toutes les données utilisateur ont été effacées");
} catch (e) { } catch (e) {
print('❌ Erreur lors de l\'effacement des données utilisateur: $e'); print('❌ Erreur lors de l\'effacement des données utilisateur: $e');
} }
@ -198,6 +223,8 @@ class UserController extends GetxController {
print("Email: ${_email.value}"); print("Email: ${_email.value}");
print("Role: ${_role.value}"); print("Role: ${_role.value}");
print("UserID: ${_userId.value}"); print("UserID: ${_userId.value}");
print("PointDeVenteID: ${_pointDeVenteId.value}");
print("PointDeVente: ${_pointDeVenteDesignation.value}");
print("IsLoggedIn: $isLoggedIn"); print("IsLoggedIn: $isLoggedIn");
print("========================"); print("========================");
} }

9
lib/main.dart

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:youmazgestion/Services/app_database.dart'; //import 'package:youmazgestion/Services/app_database.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/controller/userController.dart'; import 'package:youmazgestion/controller/userController.dart';
import 'Services/productDatabase.dart'; //import 'Services/productDatabase.dart';
import 'my_app.dart'; import 'my_app.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -14,11 +15,11 @@ void main() async {
// await AppDatabase.instance.deleteDatabaseFile(); // await AppDatabase.instance.deleteDatabaseFile();
// await ProductDatabase.instance.deleteDatabaseFile(); // await ProductDatabase.instance.deleteDatabaseFile();
await ProductDatabase.instance.initDatabase(); // await ProductDatabase.instance.initDatabase();
await AppDatabase.instance.initDatabase(); await AppDatabase.instance.initDatabase();
// Afficher les informations de la base (pour debug) // Afficher les informations de la base (pour debug)
await AppDatabase.instance.printDatabaseInfo(); // await AppDatabase.instance.printDatabaseInfo();
Get.put( Get.put(
UserController()); // Ajoute ce code AVANT tout accès au UserController UserController()); // Ajoute ce code AVANT tout accès au UserController
setupLogger(); setupLogger();

2
macos/Flutter/GeneratedPluginRegistrant.swift

@ -7,6 +7,7 @@ import Foundation
import file_picker import file_picker
import file_selector_macos import file_selector_macos
import mobile_scanner
import open_file_mac import open_file_mac
import path_provider_foundation import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
@ -16,6 +17,7 @@ import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

8
pubspec.lock

@ -608,6 +608,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.6"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: "1b60b8f9d4ce0cb0e7d7bc223c955d083a0737bee66fa1fcfe5de48225e0d5b3"
url: "https://pub.dev"
source: hosted
version: "3.5.7"
msix: msix:
dependency: "direct main" dependency: "direct main"
description: description:

1
pubspec.yaml

@ -62,6 +62,7 @@ dependencies:
path_provider: ^2.0.15 path_provider: ^2.0.15
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2
excel: ^2.0.1 excel: ^2.0.1
mobile_scanner: ^3.1.1

Loading…
Cancel
Save